Bug 1307826 - Deploy PushApkWorker on its own production machine draft
authorJohan Lorenzo <jlorenzo@mozilla.com>
Mon, 03 Oct 2016 14:49:58 +0200
changeset 4490 e6e579f1bcb74ffa4c63e557de4a0972a9d0f4d4
parent 4484 1a81d7dd16eef619d60b49757744f00e01eb5663
push id2526
push userjlorenzo@mozilla.com
push dateTue, 29 Nov 2016 09:36:58 +0000
bugs1307826
Bug 1307826 - Deploy PushApkWorker on its own production machine MozReview-Commit-ID: 5FKYwcxZSJL
manifests/moco-config.pp
manifests/moco-nodes.pp
modules/java_ks/CHANGELOG.md
modules/java_ks/CONTRIBUTING.md
modules/java_ks/Gemfile
modules/java_ks/LICENSE
modules/java_ks/README.md
modules/java_ks/Rakefile
modules/java_ks/checksums.json
modules/java_ks/lib/puppet/provider/java_ks/keytool.rb
modules/java_ks/lib/puppet/type/java_ks.rb
modules/java_ks/metadata.json
modules/java_ks/spec/acceptance/basic_spec.rb
modules/java_ks/spec/acceptance/chain_key_spec.rb
modules/java_ks/spec/acceptance/destkeypass_spec.rb
modules/java_ks/spec/acceptance/keystore_spec.rb
modules/java_ks/spec/acceptance/nodesets/centos-510-x64.yml
modules/java_ks/spec/acceptance/nodesets/centos-59-x64.yml
modules/java_ks/spec/acceptance/nodesets/centos-64-x64-pe.yml
modules/java_ks/spec/acceptance/nodesets/centos-64-x64.yml
modules/java_ks/spec/acceptance/nodesets/centos-65-x64.yml
modules/java_ks/spec/acceptance/nodesets/debian-607-x64.yml
modules/java_ks/spec/acceptance/nodesets/debian-73-x64.yml
modules/java_ks/spec/acceptance/nodesets/default.yml
modules/java_ks/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml
modules/java_ks/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml
modules/java_ks/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml
modules/java_ks/spec/acceptance/nodesets/ubuntu-server-14042-x64-vcloud.yml
modules/java_ks/spec/acceptance/nodesets/windows-2012r2-64a.yml
modules/java_ks/spec/acceptance/private_key_spec.rb
modules/java_ks/spec/acceptance/truststore_spec.rb
modules/java_ks/spec/acceptance/unsupported_spec.rb
modules/java_ks/spec/spec.opts
modules/java_ks/spec/spec_helper.rb
modules/java_ks/spec/spec_helper_acceptance.rb
modules/java_ks/spec/unit/puppet/provider/java_ks/keytool_spec.rb
modules/java_ks/spec/unit/puppet/type/java_ks_spec.rb
modules/packages/manifests/jdk17.pp
modules/pushapkworker/manifests/init.pp
modules/pushapkworker/manifests/jarsigner_init.pp
modules/pushapkworker/manifests/mime_types.pp
modules/pushapkworker/manifests/services.pp
modules/pushapkworker/manifests/settings.pp
modules/pushapkworker/templates/config.json.erb
modules/pushapkworker/templates/nagios.cfg.erb
modules/pushapkworker/templates/script_config.json.erb
modules/pushapkworker/templates/supervisor_config.erb
modules/toplevel/manifests/server/pushapkworker.pp
--- a/manifests/moco-config.pp
+++ b/manifests/moco-config.pp
@@ -443,16 +443,83 @@ class config inherits config::base {
             taskcluster_access_token => secret("beetmoverworker_dev_taskcluster_access_token"),
             beetmover_aws_access_key_id => secret("nightly-beetmover-aws_access_key_id"),
             beetmover_aws_secret_access_key => secret("nightly-beetmover-aws_secret_access_key"),
             beetmover_aws_s3_firefox_bucket => "net-mozaws-prod-delivery-firefox",
             beetmover_aws_s3_fennec_bucket => "net-mozaws-prod-delivery-archive",
         }
     }
 
+    ## TC pushapk scriptworkers
+    $pushapk_scriptworker_root = '/builds/pushapkworker'
+    $pushapk_scriptworker_worker_config = "${pushapk_scriptworker_root}/config.json"
+    $pushapk_scriptworker_script_config = "${pushapk_scriptworker_root}/script_config.json"
+
+    $pushapk_scriptworker_jarsigner_keystore = "${pushapk_scriptworker_root}/mozilla-android-keystore"
+    $pushapk_scriptworker_jarsigner_nightly_certificate_alias = 'nightly'
+    $pushapk_scriptworker_jarsigner_release_certificate_alias = 'release'
+    $pushapk_scriptworker_taskcluster_artifact_expiration_hours = 336
+    $pushapk_scriptworker_taskcluster_artifact_upload_timeout = 1200
+    $pushapk_scriptworker_task_max_timeout = 1200
+    $pushapk_scriptworker_artifact_expiration_hours = 336
+    $pushapk_scriptworker_artifact_upload_timeout = 600
+    $pushapk_scriptworker_env_config = {
+      'dev' => {
+        provisioner_id => 'scriptworker-prov-v1',
+        worker_group => 'pushapk-v1-dev',
+        worker_type => 'pushapk-v1-dev',
+        worker_id => 'jlorenzo-dev',
+        verbose_logging => true,
+        taskcluster_client_id => secret('pushapk_scriptworker_taskcluster_client_id_dev'),
+        taskcluster_access_token => secret('pushapk_scriptworker_taskcluster_access_token_dev'),
+        google_play_config => {
+          'aurora' => {
+            service_account => secret('pushapk_scriptworker_aurora_google_play_service_account_dev'),
+            certificate => secret('pushapk_scriptworker_aurora_google_play_certificate_dev'),
+            certificate_target_location => "${pushapk_scriptworker_root}/aurora.p12",
+          },
+          'beta' => {
+            service_account => secret('pushapk_scriptworker_beta_google_play_service_account_dev'),
+            certificate => secret('pushapk_scriptworker_beta_google_play_certificate_dev'),
+            certificate_target_location => "${pushapk_scriptworker_root}/beta.p12",
+          },
+          'release' => {
+            service_account => secret('pushapk_scriptworker_release_google_play_service_account_dev'),
+            certificate => secret('pushapk_scriptworker_release_google_play_certificate_dev'),
+            certificate_target_location => "${pushapk_scriptworker_root}/release.p12",
+          },
+        },
+      },
+      'prod' => {
+        provisioner_id => 'scriptworker-prov-v1',
+        worker_group => 'pushapk-v1',
+        worker_type => 'pushapk-v1',
+        verbose_logging => true,
+        taskcluster_client_id => secret('pushapk_scriptworker_taskcluster_client_id_prod'),
+        taskcluster_access_token => secret('pushapk_scriptworker_taskcluster_access_token_prod'),
+        google_play_config => {
+          'aurora' => {
+            service_account => secret('pushapk_scriptworker_aurora_google_play_service_account_prod'),
+            certificate => secret('pushapk_scriptworker_aurora_google_play_certificate_prod'),
+            certificate_target_location => "${pushapk_scriptworker_root}/aurora.p12",
+          },
+          'beta' => {
+            service_account => secret('pushapk_scriptworker_beta_google_play_service_account_prod'),
+            certificate => secret('pushapk_scriptworker_beta_google_play_certificate_prod'),
+            certificate_target_location => "${pushapk_scriptworker_root}/beta.p12",
+          },
+          'release' => {
+            service_account => secret('pushapk_scriptworker_release_google_play_service_account_prod'),
+            certificate => secret('pushapk_scriptworker_release_google_play_certificate_prod'),
+            certificate_target_location => "${pushapk_scriptworker_root}/release.p12",
+          },
+        },
+      },
+    }
+
     # Funsize Scheduler configuration
     $funsize_scheduler_root = "/builds/funsize"
     $funsize_scheduler_balrog_username = "funsize"
     $funsize_scheduler_pulse_username = "funsize"
     $funsize_scheduler_pulse_queue = "scheduler"
     $funsize_scheduler_pulse_exchange = "exchange/build"
     $funsize_scheduler_s3_bucket = "mozilla-nightly-updates"
     $funsize_scheduler_balrog_worker_api_root = "http://balrog/api"
--- a/manifests/moco-nodes.pp
+++ b/manifests/moco-nodes.pp
@@ -1173,9 +1173,17 @@ node /balrogworker-.*\.srv\.releng\..*\.
 # Beetmover scriptworkers
 node /beetmoverworker-.*\.srv\.releng\..*\.mozilla\.com/ {
     $aspects = [ 'maximum-security' ]
     $beetmoverworker_env = "dev"
     $timezone = "UTC"
     include toplevel::server::beetmoverscriptworker
 }
 
+# Pushapk scriptworkers
+node /pushapkworker-.*\.srv\.releng\..*\.mozilla\.com/ {
+    $aspects = [ 'maximum-security' ]
+    $pushapkworker_env = 'prod'
+    $timezone = 'UTC'
+    include toplevel::server::pushapkworker
+}
+
 ## Loaners
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/CHANGELOG.md
@@ -0,0 +1,150 @@
+## Supported Release 1.4.1
+### Summary
+This release contains bugfixes around certificate chains and other testing improvements.
+
+#### Bugfixes
+- Dont expose keystore content when keystore initally empty.
+- Support certificate chains in certificate file.
+- Support multiple intermediate certificates in chain.
+- Improve cert chain acceptance tests.
+- Update to current msync configs.
+- Debian 8 support.
+
+## Supported Release 1.4.0
+### Summary
+This release contains a new option to provide destkeypass. Also contains 
+bugfixes and a metadata update to support Puppet Enterprise 2015.3.x.
+
+#### Features
+- Adds `destkeypass` option to pass in password when importing into the keystore.
+- Adds feature support for JCEKS format and extensions.
+
+#### Bugfixes
+- Fixes composite title patterns in provider to improve support for Windows.
+
+#### Test Improvements
+- Improves Windows testing.
+
+## 2015-07-20 - Supported Release 1.3.1
+### Summary
+This release updates the metadata for the upcoming release of PE as well as an additional bugfix.
+
+#### Bugfixes
+- Fixes Puppet.newtype deprecation warning
+
+## 2015-04-14 - Supported Release 1.3.0
+### Summary
+Remove openssl command line tool from requirements
+
+#### Features
+- Add Windows support and tests
+
+## 2014-11-11 - Supported Release 1.2.6
+### Summary
+
+This release has test fixes and files synced from modulesync.
+
+## 2014-07-10 - Supported Release 1.2.5
+### Summary
+
+This release has bugfixes and test improvements.
+
+#### Features
+- Update tests to use RSpec 2.99 syntax
+
+#### Bugfixes
+- Remove broken support for puppet:// files.
+- Remove incorrect statment of windows support from metadata.json.
+- Fix path issue for openssl on solaris 11.
+
+#### Known Bugs
+* No known bugs
+
+## 2014-06-04 - Release 1.2.4
+### Summary
+
+This is a compatibility release. No functional changes to this module were made
+in this release, just testing infrastructure changes to extend tests to RHEL7
+and Ubuntu 14.04
+
+#### Features
+
+#### Bugfixes
+
+#### Known Bugs
+* No known bugs
+
+## 2014-03-04 - Supported Release 1.2.3
+### Summary
+
+This is a supported release.  This release removes a testing symlink that can
+cause trouble on systems where /var is on a seperate filesystem from the
+modulepath.
+
+#### Features
+
+#### Bugfixes
+
+#### Known Bugs
+* No known bugs
+
+## 2014-03-04 - Supported Release 1.2.2
+### Summary
+
+This is a supported release.  Only tests and documentation were changed.
+
+#### Features
+- Test changes.
+- Documentation changes.
+
+#### Bugfixes
+
+#### Known Bugs
+* No known bugs
+
+
+## 2014-02-12 - Release 1.2.1
+
+#### Bugfixes
+- Updating specs
+
+
+## 2013-09-18 - Release 1.2.0
+
+### Summary
+This release adds `puppet://` URI support, a few bugfixes, and lots of tests.
+
+#### Features
+- `puppet://` URI support for the `chain`, `certificate`, and `private_key` parameters
+
+#### Bugfixes
+- Validate that keystore passwords are > 6 characters (would silent fail before)
+- Fixed corrupted keystore PKCS12 files in some cases.
+- More acceptance tests, unit tests, and rspec-puppet tests.
+
+
+## 1.1.0
+
+This minor feature provides a number of new features:
+
+* We have introduced a new property `password_file` to the java_ks type, so
+  that users can specify a plain text file to be used for unlocking a Java
+  keystore file.
+* A new property `path` has been also added so you can add a custom search
+  path for the command line tooling (keystore etc.)
+
+Travis-CI support has also been added to improve testing.
+
+#### Detailed Changes
+
+* Support for executables outside the system default path (Filip Hrbek)
+* Add password_file to type (Raphaël Pinson)
+* Travis ci support (Adrien Thebo)
+* refactor keytool provider specs (Adrien Thebo)
+
+---------------------------------------
+
+## 0.0.6
+
+
+Fixes an issue with ibm java handling input from stdin on SLES
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/CONTRIBUTING.md
@@ -0,0 +1,220 @@
+Checklist (and a short version for the impatient)
+=================================================
+
+  * Commits:
+
+    - Make commits of logical units.
+
+    - Check for unnecessary whitespace with "git diff --check" before
+      committing.
+
+    - Commit using Unix line endings (check the settings around "crlf" in
+      git-config(1)).
+
+    - Do not check in commented out code or unneeded files.
+
+    - The first line of the commit message should be a short
+      description (50 characters is the soft limit, excluding ticket
+      number(s)), and should skip the full stop.
+
+    - Associate the issue in the message. The first line should include
+      the issue number in the form "(#XXXX) Rest of message".
+
+    - The body should provide a meaningful commit message, which:
+
+      - uses the imperative, present tense: "change", not "changed" or
+        "changes".
+
+      - includes motivation for the change, and contrasts its
+        implementation with the previous behavior.
+
+    - Make sure that you have tests for the bug you are fixing, or
+      feature you are adding.
+
+    - Make sure the test suites passes after your commit:
+      `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below
+
+    - When introducing a new feature, make sure it is properly
+      documented in the README.md
+
+  * Submission:
+
+    * Pre-requisites:
+
+      - Make sure you have a [GitHub account](https://github.com/join)
+
+      - [Create a ticket](https://tickets.puppetlabs.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppetlabs.com/browse/) you are patching for.
+
+    * Preferred method:
+
+      - Fork the repository on GitHub.
+
+      - Push your changes to a topic branch in your fork of the
+        repository. (the format ticket/1234-short_description_of_change is
+        usually preferred for this project).
+
+      - Submit a pull request to the repository in the puppetlabs
+        organization.
+
+The long version
+================
+
+  1.  Make separate commits for logically separate changes.
+
+      Please break your commits down into logically consistent units
+      which include new or changed tests relevant to the rest of the
+      change.  The goal of doing this is to make the diff easier to
+      read for whoever is reviewing your code.  In general, the easier
+      your diff is to read, the more likely someone will be happy to
+      review it and get it into the code base.
+
+      If you are going to refactor a piece of code, please do so as a
+      separate commit from your feature or bug fix changes.
+
+      We also really appreciate changes that include tests to make
+      sure the bug is not re-introduced, and that the feature is not
+      accidentally broken.
+
+      Describe the technical detail of the change(s).  If your
+      description starts to get too long, that is a good sign that you
+      probably need to split up your commit into more finely grained
+      pieces.
+
+      Commits which plainly describe the things which help
+      reviewers check the patch and future developers understand the
+      code are much more likely to be merged in with a minimum of
+      bike-shedding or requested changes.  Ideally, the commit message
+      would include information, and be in a form suitable for
+      inclusion in the release notes for the version of Puppet that
+      includes them.
+
+      Please also check that you are not introducing any trailing
+      whitespace or other "whitespace errors".  You can do this by
+      running "git diff --check" on your changes before you commit.
+
+  2.  Sending your patches
+
+      To submit your changes via a GitHub pull request, we _highly_
+      recommend that you have them on a topic branch, instead of
+      directly on "master".
+      It makes things much easier to keep track of, especially if
+      you decide to work on another thing before your first change
+      is merged in.
+
+      GitHub has some pretty good
+      [general documentation](http://help.github.com/) on using
+      their site.  They also have documentation on
+      [creating pull requests](http://help.github.com/send-pull-requests/).
+
+      In general, after pushing your topic branch up to your
+      repository on GitHub, you can switch to the branch in the
+      GitHub UI and click "Pull Request" towards the top of the page
+      in order to open a pull request.
+
+
+  3.  Update the related GitHub issue.
+
+      If there is a GitHub issue associated with the change you
+      submitted, then you should update the ticket to include the
+      location of your branch, along with any other commentary you
+      may wish to make.
+
+Testing
+=======
+
+Getting Started
+---------------
+
+Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby
+package manager such as [bundler](http://bundler.io/) what Ruby packages,
+or Gems, are required to build, develop, and test this software.
+
+Please make sure you have [bundler installed](http://bundler.io/#getting-started)
+on your system, then use it to install all dependencies needed for this project,
+by running
+
+```shell
+% bundle install
+Fetching gem metadata from https://rubygems.org/........
+Fetching gem metadata from https://rubygems.org/..
+Using rake (10.1.0)
+Using builder (3.2.2)
+-- 8><-- many more --><8 --
+Using rspec-system-puppet (2.2.0)
+Using serverspec (0.6.3)
+Using rspec-system-serverspec (1.0.0)
+Using bundler (1.3.5)
+Your bundle is complete!
+Use `bundle show [gemname]` to see where a bundled gem is installed.
+```
+
+NOTE some systems may require you to run this command with sudo.
+
+If you already have those gems installed, make sure they are up-to-date:
+
+```shell
+% bundle update
+```
+
+With all dependencies in place and up-to-date we can now run the tests:
+
+```shell
+% bundle exec rake spec
+```
+
+This will execute all the [rspec tests](http://rspec-puppet.com/) tests
+under [spec/defines](./spec/defines), [spec/classes](./spec/classes),
+and so on. rspec tests may have the same kind of dependencies as the
+module they are testing. While the module defines in its [Modulefile](./Modulefile),
+rspec tests define them in [.fixtures.yml](./fixtures.yml).
+
+Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker)
+tests. These tests spin up a virtual machine under
+[VirtualBox](https://www.virtualbox.org/)) with, controlling it with
+[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test
+scenarios. In order to run these, you will need both of those tools
+installed on your system.
+
+You can run them by issuing the following command
+
+```shell
+% bundle exec rake spec_clean
+% bundle exec rspec spec/acceptance
+```
+
+This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml),
+install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb)
+and then run all the tests under [spec/acceptance](./spec/acceptance).
+
+Writing Tests
+-------------
+
+XXX getting started writing tests.
+
+If you have commit access to the repository
+===========================================
+
+Even if you have commit access to the repository, you will still need to
+go through the process above, and have someone else review and merge
+in your changes.  The rule is that all changes must be reviewed by a
+developer on the project (that did not write the code) to ensure that
+all changes go through a code review process.
+
+Having someone other than the author of the topic branch recorded as
+performing the merge is the record that they performed the code
+review.
+
+
+Additional Resources
+====================
+
+* [Getting additional help](http://puppetlabs.com/community/get-help)
+
+* [Writing tests](http://projects.puppetlabs.com/projects/puppet/wiki/Development_Writing_Tests)
+
+* [Patchwork](https://patchwork.puppetlabs.com)
+
+* [General GitHub documentation](http://help.github.com/)
+
+* [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
+
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/Gemfile
@@ -0,0 +1,48 @@
+source ENV['GEM_SOURCE'] || "https://rubygems.org"
+
+def location_for(place, fake_version = nil)
+  if place =~ /^(git[:@][^#]*)#(.*)/
+    [fake_version, { :git => $1, :branch => $2, :require => false }].compact
+  elsif place =~ /^file:\/\/(.*)/
+    ['>= 0', { :path => File.expand_path($1), :require => false }]
+  else
+    [place, { :require => false }]
+  end
+end
+
+group :development, :unit_tests do
+  gem 'json',                      :require => false
+  gem 'metadata-json-lint',        :require => false
+  gem 'puppet_facts',              :require => false
+  gem 'puppet-blacksmith',         :require => false
+  gem 'puppetlabs_spec_helper',    :require => false
+  gem 'rspec-puppet', '>= 2.3.2',  :require => false
+  gem 'simplecov',                 :require => false
+end
+group :system_tests do
+  gem 'beaker-puppet_install_helper',  :require => false
+  if beaker_version = ENV['BEAKER_VERSION']
+    gem 'beaker', *location_for(beaker_version)
+  end
+  if beaker_rspec_version = ENV['BEAKER_RSPEC_VERSION']
+    gem 'beaker-rspec', *location_for(beaker_rspec_version)
+  else
+    gem 'beaker-rspec',  :require => false
+  end
+  gem 'master_manipulator',            :require => false
+  gem 'serverspec',                    :require => false
+end
+
+if facterversion = ENV['FACTER_GEM_VERSION']
+  gem 'facter', facterversion, :require => false
+else
+  gem 'facter', :require => false
+end
+
+if puppetversion = ENV['PUPPET_GEM_VERSION']
+  gem 'puppet', puppetversion, :require => false
+else
+  gem 'puppet', :require => false
+end
+
+# vim:ft=ruby
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/LICENSE
@@ -0,0 +1,15 @@
+Copyright (C) 2013 Puppet Labs Inc
+
+Puppet Labs can be contacted at: info@puppetlabs.com
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/README.md
@@ -0,0 +1,144 @@
+#java_ks
+
+[![Build Status](https://travis-ci.org/puppetlabs/puppetlabs-java_ks.png?branch=master)](https://travis-ci.org/puppetlabs/puppetlabs-java_ks)
+
+####Table of Contents
+
+1. [Overview - What is the java_ks module?](#overview)
+2. [Module Description - What does the module do?](#module-description)
+3. [Setup - The basics of getting started with java_ks](#setup)
+4. [Usage - The parameters available for configuration](#usage)
+5. [Reference - An under-the-hood peek at what the module is doing](#reference)
+6. [Limitations - OS compatibility, etc.](#limitations)
+7. [Development - Guide for contributing to the module](#development)
+
+##Overview
+
+The java_ks module uses a combination of keytool and openssl to manage entries in a Java keystore.
+
+##Module Description
+
+The java_ks module contains a type called `java_ks` and a single provider named `keytool`.  Their purpose is to enable importation of arbitrary, already generated and signed certificates into a Java keystore for use by various applications.
+
+##Setup
+
+### Beginning with java_ks
+
+To get started with java_ks, declare each `java_ks` resource you need.
+
+~~~
+java_ks { 'puppetca:truststore':
+  ensure       => latest,
+  certificate  => '/etc/puppet/ssl/certs/ca.pem',
+  target       => '/etc/activemq/broker.ts',
+  password     => 'puppet',
+  trustcacerts => true,
+}
+~~~
+
+
+##Usage
+
+You must specify a target in some way. You can specify `target` after the colon in the title or by using the target attribute in the resource. If you declare both, it will prefer the attribute.
+
+~~~
+java_ks { 'puppetca:keystore':
+  ensure       => latest,
+  certificate  => '/etc/puppet/ssl/certs/ca.pem',
+  target       => '/etc/activemq/broker.ks',
+  password     => 'puppet',
+  trustcacerts => true,
+}
+
+java_ks { 'broker.example.com:/etc/activemq/broker.ks':
+  ensure      => latest,
+  certificate => '/etc/puppet/ssl/certs/broker.example.com.pe-internal-broker.pem',
+  private_key => '/etc/puppet/ssl/private_keys/broker.example.com.pe-internal-broker.pem',
+  password    => 'puppet',
+}
+~~~
+
+### Certificates
+To have a Java application server use a specific certificate for incoming connections, use the certificate parameter. You will need to simultaneously import the private key accompanying the signed certificate you want to use. As long as you provide the path to the key and the certificate, the provider will do the conversion for you.
+
+
+### Namevars
+
+The java_ks module supports multiple certificates with different keystores but the same alias by implementing Puppet's composite namevar functionality.  Titles map to namevars via `$alias:$target` (alias of certificate, colon, on-disk path to the keystore). If you create dependencies on these resources you need to remember to use the same title syntax outlined for generating the composite namevars.
+
+*Note about composite namevars:*  
+The way composite namevars currently work, you must have the colon in the title. This is true *even if you define name and target parameters.*  The title can be `foo:bar`, but the name and target parameters must be `broker.example.com` and `/etc/activemq/broker.ks`. If you follow convention, it will do as you expect and correctly create an entry in the
+broker.ks keystore with the alias of broker.example.com.
+
+##Reference
+
+###Public Types
+* `java_ks`: This resource manages the entries in a Java keystore, and uses composite namevars to allow the same alias across multiple target keystores.
+
+###Public Providers
+* `keytool`: Manages Java keystores by using a combination of the `openssl` and `keytool` commands.
+
+####Parameters
+All parameters, except where specified, are optional.
+
+#####`certificate`
+*Required.* A server certificate, followed by zero or more intermediate certificate authorities. Places the certificates in the keystore. This autorequires the specified file and must be present on the node before java_ks{} is run. Valid options: string. Default: undef.
+
+#####`chain`
+Takes intermediate certificate authorities from a separate file from the server certificate. This autorequires the file of the same path and must be present on the node before java_ks{} is run. Valid options: string. Default: undef.
+
+#####`ensure`
+Valid options: absent, present, latest. Latest verifies md5 certificate fingerprints for the stored certificate and the source file. Default: present.
+
+#####`name`
+*Required.* Identifies the entry in the keystore. This will be converted to lowercase. Valid options: string. Default: undef.  
+
+#####`password`
+This password is used to protect the keystore. If private keys are also protected, this password will be used to attempt to unlock them. Valid options: String. Must be 6 or more characters. This cannot be used together with `password_file`, but *you must pass at least one of these parameters.* Default: undef.
+
+#####`password_file`
+Sets a plaintext file where the password is stored. Used as an alternative to `password`. This cannot be used together with `password`, but *you must pass at least one of these parameters.* Valid options: String to the plaintext file. Default: undef.
+
+#####`destkeypass`
+The password you want to set to protect the key in the keystore.
+
+#####`path`
+Used for command (keytool, openssl) execution. Valid options: array or file path separated list (for example : in linux). Default: undef.
+
+#####`private_key`
+Sets a private key that encrypts traffic to a server application. Must be accompanied by a signed certificate for the keytool provider. This autorequires the specified file and must be present on the node before java_ks{} is run. Valid options: string. Default: undef.
+
+#####`target`
+*Required.* Specifies a destination file for the keystore. Autorequires the parent directory of the file. Valid options: string. Default: undef.
+
+#####`trustcacerts`
+Certificate authorities input into a keystore aren’t trusted by default, so if you are adding a CA you need to set this parameter to 'true'. Valid options: 'true' or 'false'. Default: 'false'.
+
+### `storetype`
+
+The storetype parameter allows you to use 'jceks' format if desired.
+
+    java_ks { 'puppetca:/opt/puppet/truststore.jceks':
+      ensure       => latest,
+      storetype    => 'jceks',
+      certificate  => '/etc/puppet/ssl/certs/ca.pem',
+      password     => 'puppet',
+      trustcacerts => true,
+    }
+
+
+Limitations
+------------
+
+The java_ks module uses the `keytool` and `openssl` commands. It should work on all systems with these commands.
+
+Java 7 is supported as of 1.0.0.
+
+Developed against IBM Java 6 on AIX. Other versions may be unsupported.
+
+Development
+-----------
+
+Puppet Labs modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can’t access the huge number of platforms and myriad hardware, software, and deployment configurations that Puppet is intended to serve.  
+
+We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. For more information, see our [module contribution guide.](https://docs.puppetlabs.com/forge/contributing.html)
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/Rakefile
@@ -0,0 +1,11 @@
+require 'puppet_blacksmith/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+require 'puppetlabs_spec_helper/rake_tasks'
+
+PuppetLint.configuration.fail_on_warnings = true
+PuppetLint.configuration.send('relative')
+PuppetLint.configuration.send('disable_80chars')
+PuppetLint.configuration.send('disable_class_inherits_from_params_class')
+PuppetLint.configuration.send('disable_documentation')
+PuppetLint.configuration.send('disable_single_quote_string_with_variables')
+PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"]
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/checksums.json
@@ -0,0 +1,36 @@
+{
+  "CHANGELOG.md": "a803d130ca9152c53143a507ef724659",
+  "CONTRIBUTING.md": "ad65d271f183b5adb9fdd58207939f5f",
+  "Gemfile": "4c929078ebcde3437eacd8b1d2687d71",
+  "LICENSE": "08a92c4b34dd9392acbcfec2ae2f27db",
+  "README.md": "35d90ab1c5054f1bf650c1d8a46442a4",
+  "Rakefile": "15f2f143e7515282edf801dc7e94a544",
+  "lib/puppet/provider/java_ks/keytool.rb": "4d6e70505fb63e6e054f90960b3dc827",
+  "lib/puppet/type/java_ks.rb": "65fa78e1cf1f1130cb1a3f15bccd7c0b",
+  "metadata.json": "f183ede08c0e672f94109e5ab476e1ad",
+  "spec/acceptance/basic_spec.rb": "480710e1e4f388d5f7e1e5edf4e80935",
+  "spec/acceptance/chain_key_spec.rb": "2ab4f508c7209b44fe52669bddd49d09",
+  "spec/acceptance/destkeypass_spec.rb": "1e03459312b5f6932aed14ba24a23076",
+  "spec/acceptance/keystore_spec.rb": "06130dc5f662885dc23b04ef1cf5b707",
+  "spec/acceptance/nodesets/centos-510-x64.yml": "eeac8e383077addbe5e3c415da92d907",
+  "spec/acceptance/nodesets/centos-59-x64.yml": "57eb3e471b9042a8ea40978c467f8151",
+  "spec/acceptance/nodesets/centos-64-x64-pe.yml": "ec075d95760df3d4702abea1ce0a829b",
+  "spec/acceptance/nodesets/centos-64-x64.yml": "bf73f492aa4599fd16fce652b4f315d4",
+  "spec/acceptance/nodesets/centos-65-x64.yml": "3e5c36e6aa5a690229e720f4048bb8af",
+  "spec/acceptance/nodesets/debian-607-x64.yml": "016094fb20069d20fa919dd17dcac61f",
+  "spec/acceptance/nodesets/debian-73-x64.yml": "d1b9217880a8f05bacd69edd01abe7a5",
+  "spec/acceptance/nodesets/default.yml": "bf73f492aa4599fd16fce652b4f315d4",
+  "spec/acceptance/nodesets/ubuntu-server-10044-x64.yml": "75e86400b7889888dc0781c0ae1a1297",
+  "spec/acceptance/nodesets/ubuntu-server-12042-x64.yml": "d30d73e34cd50b043c7d14e305955269",
+  "spec/acceptance/nodesets/ubuntu-server-1404-x64.yml": "5f0aed10098ac5b78e4217bb27c7aaf0",
+  "spec/acceptance/nodesets/ubuntu-server-14042-x64-vcloud.yml": "5361610efb147478e2eb24bfd36cf584",
+  "spec/acceptance/nodesets/windows-2012r2-64a.yml": "d1d59c8e95a5e06b2238dd0c14bd6573",
+  "spec/acceptance/private_key_spec.rb": "56fed8cf193689dc8b063fc71eb12fdc",
+  "spec/acceptance/truststore_spec.rb": "ba645689af591b2af54ff6f8a3405cf7",
+  "spec/acceptance/unsupported_spec.rb": "42d03ab96773b73b5a8b08694579d4a6",
+  "spec/spec.opts": "a600ded995d948e393fbe2320ba8e51c",
+  "spec/spec_helper.rb": "2c6d82bcf3d61771154b1a2a4c3527f9",
+  "spec/spec_helper_acceptance.rb": "40f541da8fd56395d4c421aa2e639311",
+  "spec/unit/puppet/provider/java_ks/keytool_spec.rb": "3eacf9798b22feca3d3ca0cbec0c4cae",
+  "spec/unit/puppet/type/java_ks_spec.rb": "d825f963999b3e54b8820273d734ef74"
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/lib/puppet/provider/java_ks/keytool.rb
@@ -0,0 +1,282 @@
+require 'openssl'
+require 'puppet/util/filetype'
+
+Puppet::Type.type(:java_ks).provide(:keytool) do
+  desc 'Uses a combination of openssl and keytool to manage Java keystores'
+
+  def command_keytool
+    'keytool'
+  end
+
+  # Keytool can only import a keystore if the format is pkcs12.  Generating and
+  # importing a keystore is used to add private_key and certifcate pairs.
+  def to_pkcs12(path)
+    pkey = OpenSSL::PKey::RSA.new File.read private_key
+    if chain
+      x509_cert = OpenSSL::X509::Certificate.new File.read certificate
+      chain_certs = get_chain(chain)
+    else
+      chain_certs = get_chain(certificate)
+      x509_cert = chain_certs.shift
+    end
+    pkcs12 = OpenSSL::PKCS12.create(get_password, @resource[:name], pkey, x509_cert, chain_certs)
+    File.open(path, "wb") { |f| f.print pkcs12.to_der }
+  end
+
+  # Keytool can only import a jceks keystore if the format is der.  Generating and
+  # importing a keystore is used to add private_key and certifcate pairs.
+  def to_der(path)
+    x509_cert = OpenSSL::X509::Certificate.new File.read certificate
+    File.open(path, "wb") { |f| f.print x509_cert.to_der }
+  end
+
+  def get_chain(path)
+    File.read(path).scan(/-----BEGIN [^\n]*CERTIFICATE.*?-----END [^\n]*CERTIFICATE-----/m).map {|cert| OpenSSL::X509::Certificate.new cert}
+  end
+
+  def get_password
+    if @resource[:password_file].nil?
+      @resource[:password]
+    else
+      file = File.open(@resource[:password_file], "r")
+      pword = file.read
+      file.close
+      pword.chomp
+    end
+  end
+
+  def password_file
+    pword = get_password
+
+    tmpfile = Tempfile.new("#{@resource[:name]}.")
+    if File.exists?(@resource[:target]) and not File.zero?(@resource[:target])
+      tmpfile.write("#{pword}\n#{pword}")
+    else
+      tmpfile.write("#{pword}\n#{pword}\n#{pword}")
+    end
+    tmpfile.flush
+    tmpfile
+  end
+
+  # Where we actually to the import of the file created using to_pkcs12.
+  def import_ks
+    tmppk12 = Tempfile.new("#{@resource[:name]}.")
+    to_pkcs12(tmppk12.path)
+    cmd = [
+        command_keytool,
+        '-importkeystore', '-srcstoretype', 'PKCS12',
+        '-destkeystore', @resource[:target],
+        '-srckeystore', tmppk12.path,
+        '-alias', @resource[:name]
+    ]
+    cmd << '-trustcacerts' if @resource[:trustcacerts] == :true
+    cmd += [ '-destkeypass', @resource[:destkeypass] ] unless @resource[:destkeypass].nil?
+
+    pwfile = password_file
+    run_command(cmd, @resource[:target], pwfile)
+    tmppk12.close!
+    pwfile.close! if pwfile.is_a? Tempfile
+  end
+
+  def import_jceks
+    tmpder = Tempfile.new("#{@resource[:name]}.")
+    to_der(tmpder.path)
+    cmd = [
+	command_keytool,
+	'-importcert', '-noprompt',
+	'-alias', @resource[:name],
+	'-file', tmpder.path,
+	'-keystore', @resource[:target],
+	'-storetype', storetype
+    ]
+    cmd << '-trustcacerts' if @resource[:trustcacerts] == :true
+    cmd += [ '-destkeypass', @resource[:destkeypass] ] unless @resource[:destkeypass].nil?
+
+    pwfile = password_file
+    run_command(cmd, @resource[:target], pwfile)
+    pwfile.close! if pwfile.is_a? Tempfile
+  end
+
+  def exists?
+    cmd = [
+        command_keytool,
+        '-list',
+        '-keystore', @resource[:target],
+        '-alias', @resource[:name]
+    ]
+    cmd += [ '-storetype', storetype ] if storetype == "jceks"
+    begin
+      tmpfile = password_file
+      run_command(cmd, false, tmpfile)
+      tmpfile.close!
+      return true
+    rescue
+      return false
+    end
+  end
+
+  # Reading the fingerprint of the certificate on disk.
+  def latest
+    # The certificate file may not exist during a puppet noop run as it's managed by puppet.
+    # Return value must be different to provider.current to signify a possible trigger event.
+    if Puppet[:noop] and !File.exists?(certificate)
+      return 'latest'
+    else
+      cmd = [
+          command_keytool,
+          '-v', '-printcert', '-file', certificate
+      ]
+      output = run_command(cmd)
+      latest = output.scan(/MD5:\s+(.*)/)[0][0]
+      return latest
+    end
+  end
+
+  # Reading the fingerprint of the certificate currently in the keystore.
+  def current
+    # The keystore file may not exist during a puppet noop run as it's managed by puppet.
+    if Puppet[:noop] and !File.exists?(@resource[:target])
+      return 'current'
+    else
+      cmd = [
+          command_keytool,
+          '-list', '-v',
+          '-keystore', @resource[:target],
+          '-alias', @resource[:name]
+      ]
+      cmd += [ '-storetype', storetype ] if storetype == "jceks"
+      tmpfile = password_file
+      output = run_command(cmd, false, tmpfile)
+      tmpfile.close!
+      current = output.scan(/Certificate fingerprints:\n\s+MD5:  (.*)/)[0][0]
+      return current
+    end
+  end
+
+  # Determine if we need to do an import of a private_key and certificate pair
+  # or just add a signed certificate, then do it.
+  def create
+    if !certificate.nil? and !private_key.nil?
+      import_ks
+    elsif certificate.nil? and !private_key.nil?
+      raise Puppet::Error, 'Keytool is not capable of importing a private key without an accomapaning certificate.'
+    elsif storetype == "jceks"
+      import_jceks
+    else
+      cmd = [
+          command_keytool,
+          '-importcert', '-noprompt',
+          '-alias', @resource[:name],
+          '-file', certificate,
+          '-keystore', @resource[:target]
+      ]
+      cmd << '-trustcacerts' if @resource[:trustcacerts] == :true
+      tmpfile = password_file
+      run_command(cmd, @resource[:target], tmpfile)
+      tmpfile.close!
+    end
+  end
+
+  def destroy
+    cmd = [
+        command_keytool,
+        '-delete',
+        '-alias', @resource[:name],
+        '-keystore', @resource[:target]
+    ]
+    tmpfile = password_file
+    run_command(cmd, false, tmpfile)
+    tmpfile.close!
+  end
+
+  # Being safe since I have seen some additions overwrite and some just throw errors.
+  def update
+    destroy
+    create
+  end
+
+  def certificate
+    @resource[:certificate]
+  end
+
+  def private_key
+    @resource[:private_key]
+  end
+
+  def chain
+    @resource[:chain]
+  end
+
+  def storetype
+    @resource[:storetype]
+  end
+
+  def run_command(cmd, target=false, stdinfile=false, env={})
+
+    env[:PATH] = @resource[:path].join(File::PATH_SEPARATOR) if resource[:path]
+
+    # The Puppet::Util::Execution.execute method is deparcated in Puppet 3.x
+    # but we need this to work on 2.7.x too.
+    if Puppet::Util::Execution.respond_to?(:execute)
+      exec_method = Puppet::Util::Execution.method(:execute)
+    else
+      exec_method = Puppet::Util.method(:execute)
+    end
+
+    if Puppet::Util::Execution.respond_to?(:withenv)
+      withenv = Puppet::Util::Execution.method(:withenv)
+    else
+      withenv = Puppet::Util.method(:withenv)
+    end
+
+    # the java keytool will not correctly deal with an empty target keystore
+    # file. If we encounter an empty keystore target file, preserve the mode,
+    # owner and group, temporarily raise the umask, and delete the empty file.
+    if target and (File.exists?(target) and File.zero?(target))
+      stat = File.stat(target)
+      umask = File.umask(0077)
+      File.delete(target)
+    end
+
+    # There's a problem in IBM java keytool wherein stdin cannot be used
+    # (trivially) to pass in the keystore passwords. The below hack makes the
+    # provider work on SLES with minimal effort at the cost of letting the
+    # passphrase to the keystore show up in the process list as an argument.
+    # From a best practice standpoint the keystore should be protected by file
+    # permissions and not just the passphrase so "making it work on SLES"
+    # trumps.
+    if Facter.value('osfamily') == 'Suse' and @resource[:password]
+      cmd_to_run = cmd.is_a?(String) ? cmd.split(/\s/).first : cmd.first
+      if cmd_to_run == command_keytool
+        cmd << '-srcstorepass' << @resource[:password]
+        cmd << '-deststorepass' << @resource[:password]
+      end
+    end
+
+    # Now run the command
+    options = {:failonfail => true, :combine => true}
+    output = if stdinfile
+               withenv.call(env) do
+                 exec_method.call(cmd, options.merge(:stdinfile => stdinfile.path))
+               end
+             else
+               withenv.call(env) do
+                 exec_method.call(cmd, options)
+               end
+             end
+
+    # for previously empty files, restore the umask, mode, owner and group.
+    # The funky double-take check is because on Suse defined? doesn't seem
+    # to behave quite the same as on Debian, RedHat
+    if target and (defined? stat and stat)
+      File.umask(umask)
+      # Need to change group ownership before mode to prevent making the file
+      # accessible to the wrong group.
+      File.chown(stat.uid, stat.gid, target)
+      File.chmod(stat.mode, target)
+    end
+
+    return output
+  end
+
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/lib/puppet/type/java_ks.rb
@@ -0,0 +1,187 @@
+Puppet::Type.newtype(:java_ks) do
+  @doc = 'Manages the entries in a java keystore, and uses composite namevars to
+  accomplish the same alias spread across multiple target keystores.'
+
+  ensurable do
+
+    desc 'Has three states: present, absent, and latest.  Latest
+      will compare the on disk MD5 fingerprint of the certificate to that
+      in keytool to determine if insync? returns true or false.  We redefine
+      insync? for this paramerter to accomplish this.'
+
+    newvalue(:present) do
+      provider.create
+    end
+
+    newvalue(:absent) do
+      provider.destroy
+    end
+
+    newvalue(:latest) do
+      if provider.exists?
+        provider.update
+      else
+        provider.create
+      end
+    end
+
+    def insync?(is)
+
+      @should.each do |should|
+        case should
+        when :present
+          return true if is == :present
+        when :absent
+          return true if is == :absent
+        when :latest
+          unless is == :absent
+            return true if provider.latest == provider.current
+          end
+        end
+      end
+
+      return false
+    end
+
+    defaultto :present
+  end
+
+  newparam(:name) do
+    desc 'The alias that is used to identify the entry in the keystore. This will be
+    converted to lowercase.'
+
+    isnamevar
+
+    munge do |value|
+      value.downcase
+    end
+  end
+
+  newparam(:target) do
+    desc 'Destination file for the keystore.  This will autorequire the parent directory of the file.'
+
+    isnamevar
+  end
+
+  newparam(:certificate) do
+    desc 'A server certificate, followed by zero or more intermediate certificate authorities.
+      All certificates will be placed in the keystore.  This will autorequire the specified file.'
+
+    isrequired
+  end
+
+  newparam(:storetype) do
+    desc 'Optional storetype
+      Valid options: <jceks>'
+
+    newvalues(:jceks)
+  end
+
+  newparam(:private_key) do
+    desc 'If you want an application to be a server and encrypt traffic,
+      you will need a private key.  Private key entries in a keystore must be
+      accompanied by a signed certificate for the keytool provider. This will autorequire the specified file.'
+  end
+
+  newparam(:chain) do
+    desc 'The intermediate certificate authorities, if they are to be taken
+      from a file separate from the server certificate. This will autorequire the specified file.'
+  end
+
+  newparam(:password) do
+    desc 'The password used to protect the keystore.  If private keys are
+      subsequently also protected this password will be used to attempt
+      unlocking. Must be six or more characters in length. Cannot be used
+      together with :password_file, but you must pass at least one of these parameters.'
+
+    validate do |value|
+      raise Puppet::Error, "password is #{value.length} characters long; must be 6 characters or greater in length" if value.length < 6
+    end
+  end
+
+  newparam(:password_file) do
+    desc 'The path to a file containing the password used to protect the
+      keystore. This cannot be used together with :password, but you must pass at least one of these parameters.'
+  end
+
+  newparam(:destkeypass) do
+    desc 'The password used to protect the key in keystore.'
+
+    validate do |value|
+      raise Puppet::Error, "destkeypass is #{value.length} characters long; must be of length 6 or greater" if value.length < 6
+    end
+  end
+
+  newparam(:trustcacerts) do
+    desc "Certificate authorities aren't by default trusted so if you are adding a CA you need to set this to true.
+     Defaults to :false."
+
+    newvalues(:true, :false)
+
+    defaultto :false
+  end
+
+  newparam(:path) do
+    desc "The search path used for command (keytool, openssl) execution.
+      Paths can be specified as an array or as a '#{File::PATH_SEPARATOR}' separated list."
+
+    # Support both arrays and colon-separated fields.
+    def value=(*values)
+      @value = values.flatten.collect { |val|
+        val.split(File::PATH_SEPARATOR)
+      }.flatten
+    end
+  end
+
+  # Where we setup autorequires.
+  autorequire(:file) do
+    auto_requires = []
+    [:private_key, :certificate, :chain].each do |param|
+      if @parameters.include?(param)
+        auto_requires << @parameters[param].value
+      end
+    end
+    if @parameters.include?(:target)
+      auto_requires << ::File.dirname(@parameters[:target].value)
+    end
+    auto_requires
+  end
+
+  # Our title_patterns method for mapping titles to namevars for supporting
+  # composite namevars.
+  def self.title_patterns
+    identity = lambda {|x| x}
+    [
+      [
+        /^([^:]+)$/,
+        [
+          [ :name, identity ]
+        ]
+      ],
+      [
+        /^(.*):([a-z]:(\/|\\).*)$/i,
+        [
+            [ :name, identity ],
+            [ :target, identity ]
+        ]
+      ],
+      [
+        /^(.*):(.*)$/,
+        [
+          [ :name, identity ],
+          [ :target, identity ]
+        ]
+      ]
+    ]
+  end
+
+  validate do
+    if value(:password) and value(:password_file)
+      self.fail "You must pass either 'password' or 'password_file', not both."
+    end
+
+    unless value(:password) or value(:password_file)
+      self.fail "You must pass one of 'password' or 'password_file'."
+    end
+  end
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/metadata.json
@@ -0,0 +1,109 @@
+{
+  "name": "puppetlabs-java_ks",
+  "version": "1.4.1",
+  "author": "puppetlabs",
+  "summary": "Manage arbitrary Java keystore files",
+  "license": "Apache-2.0",
+  "source": "https://github.com/puppetlabs/puppetlabs-java_ks.git",
+  "project_page": "https://github.com/puppetlabs/puppetlabs-java_ks",
+  "issues_url": "https://tickets.puppetlabs.com/browse/MODULES",
+  "dependencies": [
+  
+  ],
+  "data_provider": null,
+  "operatingsystem_support": [
+    {
+      "operatingsystem": "RedHat",
+      "operatingsystemrelease": [
+        "5",
+        "6",
+        "7"
+      ]
+    },
+    {
+      "operatingsystem": "CentOS",
+      "operatingsystemrelease": [
+        "5",
+        "6",
+        "7"
+      ]
+    },
+    {
+      "operatingsystem": "OracleLinux",
+      "operatingsystemrelease": [
+        "5",
+        "6",
+        "7"
+      ]
+    },
+    {
+      "operatingsystem": "Scientific",
+      "operatingsystemrelease": [
+        "5",
+        "6",
+        "7"
+      ]
+    },
+    {
+      "operatingsystem": "SLES",
+      "operatingsystemrelease": [
+        "10 SP4",
+        "11 SP1",
+        "12"
+      ]
+    },
+    {
+      "operatingsystem": "Debian",
+      "operatingsystemrelease": [
+        "6",
+        "7",
+        "8"
+      ]
+    },
+    {
+      "operatingsystem": "Ubuntu",
+      "operatingsystemrelease": [
+        "10.04",
+        "12.04",
+        "14.04"
+      ]
+    },
+    {
+      "operatingsystem": "Solaris",
+      "operatingsystemrelease": [
+        "10",
+        "11"
+      ]
+    },
+    {
+      "operatingsystem": "AIX",
+      "operatingsystemrelease": [
+        "6.1",
+        "7.1"
+      ]
+    },
+    {
+      "operatingsystem": "Windows",
+      "operatingsystemrelease": [
+        "Server 2003 R2",
+        "Server 2008 R2",
+        "Server 2012",
+        "Server 2012 R2",
+        "7",
+        "8",
+        "8.1"
+      ]
+    }
+  ],
+  "requirements": [
+    {
+      "name": "pe",
+      "version_requirement": ">= 3.0.0 < 2015.4.0"
+    },
+    {
+      "name": "puppet",
+      "version_requirement": ">= 3.0.0 < 5.0.0"
+    }
+  ],
+  "description": "Uses a combination of keytool and Ruby openssl library to manage entries in a Java keystore."
+}
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/basic_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper_acceptance'
+
+describe 'prep nodes', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+  it 'requires java', :unless => ["Solaris", "AIX"].include?(fact('osfamily')) do
+    java_source = ENV['JAVA_DOWNLOAD_SOURCE'] || "http://download.oracle.com/otn-pub/java/jdk/7u67-b01/jdk-7u67-windows-x64.exe"
+    java_major, java_minor = (ENV['JAVA_VERSION'] || '7u67').split('u')
+    pp = <<-EOS
+if $::osfamily !~ /windows/ {
+  class { 'java': }
+} else {
+  windows_java::jdk{'JDK #{java_major}u#{java_minor}':
+	  ensure       => 'present',
+		install_name => 'Java SE Development Kit #{java_major} Update #{java_minor} (64-bit)',
+		source       => '#{java_source}',
+		install_path => 'C:\\Java\\jdk1.#{java_major}.0_#{java_minor}',
+  }
+}
+    EOS
+    apply_manifest(pp, :catch_failures => true)
+  end
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/chain_key_spec.rb
@@ -0,0 +1,151 @@
+require 'spec_helper_acceptance'
+
+hostname = default.node_name
+
+describe 'managing combined java chain keys', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+  include_context 'common variables'
+
+  case fact('osfamily')
+    when "windows"
+      target = 'c:/chain_combined_key.ks'
+    else
+      target = '/etc/chain_combined_key.ks'
+  end
+  it 'creates a private key with chain certs' do
+    pp = <<-EOS
+      java_ks { 'broker.example.com:#{target}':
+        ensure       => latest,
+        certificate  => "#{@temp_dir}leafchain.pem",
+        private_key  => "#{@temp_dir}leafkey.pem",
+        password     => 'puppet',
+        path         => #{@resource_path},
+      }
+    EOS
+
+    apply_manifest(pp, :catch_failures => true)
+  end
+
+  it 'verifies the private key' do
+    shell("#{@keytool_path}keytool -list -v -keystore #{target} -storepass puppet") do |r|
+      expect(r.exit_code).to be_zero
+      expect(r.stdout).to match(/Alias name: broker\.example\.com/)
+      expect(r.stdout).to match(/Entry type: (keyEntry|PrivateKeyEntry)/)
+      expect(r.stdout).to match(/Certificate chain length: 3/)
+      expect(r.stdout).to match(/^Serial number: 5$.*^Serial number: 4$.*^Serial number: 3$/m)
+    end
+  end
+end
+
+describe 'managing separate java chain keys', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+  include_context 'common variables'
+
+  case fact('osfamily')
+    when "windows"
+      target = 'c:/chain_key.ks'
+    else
+      target = '/etc/chain_key.ks'
+  end
+  it 'creates a private key with chain certs' do
+    pp = <<-EOS
+      java_ks { 'broker.example.com:#{target}':
+        ensure       => latest,
+        certificate  => "#{@temp_dir}leaf.pem",
+        chain        => "#{@temp_dir}chain.pem",
+        private_key  => "#{@temp_dir}leafkey.pem",
+        password     => 'puppet',
+        path         => #{@resource_path},
+      }
+    EOS
+
+    apply_manifest(pp, :catch_failures => true)
+  end
+
+  it 'verifies the private key' do
+    shell("#{@keytool_path}keytool -list -v -keystore #{target} -storepass puppet") do |r|
+      expect(r.exit_code).to be_zero
+      expect(r.stdout).to match(/Alias name: broker\.example\.com/)
+      expect(r.stdout).to match(/Entry type: (keyEntry|PrivateKeyEntry)/)
+      expect(r.stdout).to match(/Certificate chain length: 3/)
+      expect(r.stdout).to match(/^Serial number: 5$.*^Serial number: 4$.*^Serial number: 3$/m)
+    end
+  end
+end
+
+describe 'managing non existent java chain keys in noop', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+  include_context 'common variables'
+
+  case fact('osfamily')
+    when "windows"
+      target = 'c:/noop_chain_key.ks'
+      temp_dir = 'C:/tmp/'
+    else
+      target = '/etc/noop_chain_key.ks'
+      temp_dir = '/tmp/'
+  end
+  it 'does not create a new keystore in noop' do
+    pp = <<-EOS
+      $filenames = ["#{temp_dir}noop_ca.pem",
+                    "#{temp_dir}noop_chain.pem",
+                    "#{temp_dir}noop_privkey.pem"]
+      file { $filenames:
+        ensure  => file,
+        content => 'content',
+      } ->
+      java_ks { 'broker.example.com:#{target}':
+        ensure       => latest,
+        certificate  => "#{temp_dir}noop_ca.pem",
+        chain        => "#{temp_dir}noop_chain.pem",
+        private_key  => "#{temp_dir}noop_privkey.pem",
+        password     => 'puppet',
+        path         => #{@resource_path},
+      }
+    EOS
+
+    # in noop mode, when the dependent certificate files are not present in the system,
+    # java_ks will not invoke openssl to validate their status, thus noop will succeed
+    apply_manifest(pp, :catch_failures => true, :noop => true)
+  end
+
+  # verifies the dependent files are missing
+  ["#{temp_dir}noop_ca.pem", "#{temp_dir}noop_chain.pem", "#{temp_dir}noop_privkey.pem"].each do |filename|
+    describe file("#{filename}") do
+      it { should_not be_file }
+    end
+  end
+
+  # verifies the keystore is not created
+  describe file("#{target}") do
+    it { should_not be_file }
+  end
+end
+
+describe 'managing existing java chain keys in noop', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+  include_context 'common variables'
+
+  case fact('osfamily')
+    when "windows"
+      target = 'c:/noop2_chain_key.ks'
+    else
+      target = '/etc/noop2_chain_key.ks'
+  end
+  it 'does not create a new keystore in noop' do
+    pp = <<-EOS
+      java_ks { 'broker.example.com:#{target}':
+        ensure       => latest,
+        certificate  => "#{@temp_dir}leaf.pem",
+        chain        => "#{@temp_dir}chain.pem",
+        private_key  => "#{@temp_dir}leafkey.pem",
+        password     => 'puppet',
+        path         => #{@resource_path},
+      }
+    EOS
+
+    apply_manifest(pp, :catch_failures => true, :noop => true)
+  end
+
+  # in noop mode, when the dependent certificate files are present in the system,
+  # java_ks will invoke openssl to validate their status, but will not create the keystore
+  describe file("#{target}") do
+    it { should_not be_file }
+  end
+end
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/destkeypass_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper_acceptance'
+
+hostname = default.node_name
+
+describe 'password protected java private keys', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+  include_context 'common variables'
+
+  let(:confdir)    { default['puppetpath']    }
+  let(:modulepath) { default['distmoduledir'] }
+
+  case fact('osfamily')
+    when "windows"
+      target = 'c:/private_key.ks'
+    else
+      target = '/etc/private_key.ks'
+  end
+
+  it 'creates a password protected private key' do
+    pp = <<-EOS
+      java_ks { 'broker.example.com:#{target}':
+        ensure       => latest,
+        certificate  => "#{@temp_dir}ca.pem",
+        private_key  => "#{@temp_dir}privkey.pem",
+        password     => 'testpass',
+        destkeypass  => 'testkeypass',
+        path         => #{@resource_path},
+      }
+    EOS
+
+    apply_manifest(pp, :catch_failures => true)
+  end
+
+  it 'can make a cert req with the right password' do
+    shell("#{@keytool_path}keytool -certreq -alias broker.example.com -v "\
+     "-keystore #{target} -storepass testpass -keypass testkeypass") do |r|
+      expect(r.exit_code).to be_zero
+      expect(r.stdout).to match(/-BEGIN NEW CERTIFICATE REQUEST-/)
+    end
+  end
+
+  it 'cannot make a cert req with the wrong password' do
+    shell("#{@keytool_path}keytool -certreq -alias broker.example.com -v "\
+     "-keystore #{target} -storepass testpass -keypass qwert",
+     :acceptable_exit_codes => 1)
+  end
+
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/keystore_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper_acceptance'
+
+describe 'managing java keystores', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+  include_context 'common variables'
+
+  case fact('osfamily')
+    when 'windows'
+      target = 'c:/tmp/keystore.ks'
+    else
+      target = '/etc/keystore.ks'
+  end
+
+  describe 'basic tests' do
+    it 'should create a keystore' do
+      pp = <<-EOS
+        java_ks { 'puppetca:keystore':
+          ensure       => latest,
+          certificate  => "#{@temp_dir}ca.pem",
+          target       => '#{target}',
+          password     => 'puppet',
+          trustcacerts => true,
+          path         => #{@resource_path},
+        }
+      EOS
+
+      apply_manifest(pp, :catch_failures => true)
+      apply_manifest(pp, :catch_changes => true)
+    end
+
+    it 'verifies the keystore' do
+      shell("#{@keytool_path}keytool -list -v -keystore #{target} -storepass puppet") do |r|
+        expect(r.exit_code).to be_zero
+        expect(r.stdout).to match(/Your keystore contains 1 entry/)
+        expect(r.stdout).to match(/Alias name: puppetca/)
+        expect(r.stdout).to match(/CN=Test CA/)
+      end
+    end
+
+    it 'uses password_file' do
+      pp = <<-EOS
+        file { '#{@temp_dir}password':
+          ensure  => file,
+          content => 'puppet',
+        }
+        java_ks { 'puppetca2:keystore':
+          ensure        => latest,
+          certificate   => "#{@temp_dir}ca2.pem",
+          target        => '#{target}',
+          password_file => '#{@temp_dir}password',
+          trustcacerts  => true,
+          path          => #{@resource_path},
+          require       => File['#{@temp_dir}password']
+        }
+      EOS
+
+      apply_manifest(pp, :catch_failures => true)
+      apply_manifest(pp, :catch_changes => true)
+    end
+  end
+
+  describe 'storetype' do
+    it 'should create a keystore' do
+      pp = <<-EOS
+        java_ks { 'puppetca:keystore':
+          ensure       => latest,
+          certificate  => "#{@temp_dir}ca.pem",
+          target       => '#{target}',
+          password     => 'puppet',
+          trustcacerts => true,
+          path         => #{@resource_path},
+          storetype    => 'jceks',
+        }
+      EOS
+
+      apply_manifest(pp, :catch_failures => true)
+      apply_manifest(pp, :catch_changes => true)
+    end
+
+    it 'verifies the keystore' do
+      shell("#{@keytool_path}keytool -list -v -keystore #{target} -storepass puppet") do |r|
+        expect(r.exit_code).to be_zero
+        expect(r.stdout).to match(/Your keystore contains 2 entries/)
+        expect(r.stdout).to match(/Alias name: puppetca/)
+        expect(r.stdout).to match(/CN=Test CA/)
+      end
+    end
+  end
+
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/centos-510-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+  centos-510-x64.local:
+    roles:
+      - master
+    platform: el-5-x86_64
+    box : centos-510-x64-virtualbox-nocm
+    box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-510-x64-virtualbox-nocm.box
+    hypervisor : vagrant
+CONFIG:
+  type: git
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/centos-59-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+  centos-59-x64:
+    roles:
+      - master
+    platform: el-5-x86_64
+    box : centos-59-x64-vbox4210-nocm
+    box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-59-x64-vbox4210-nocm.box
+    hypervisor : vagrant
+CONFIG:
+  type: git
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/centos-64-x64-pe.yml
@@ -0,0 +1,12 @@
+HOSTS:
+  centos-64-x64:
+    roles:
+      - master
+      - database
+      - dashboard
+    platform: el-6-x86_64
+    box : centos-64-x64-vbox4210-nocm
+    box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
+    hypervisor : vagrant
+CONFIG:
+  type: pe
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/centos-64-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+  centos-64-x64.local:
+    roles:
+      - master
+    platform: el-6-x86_64
+    box : centos-64-x64-vbox4210-nocm
+    box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
+    hypervisor : vagrant
+CONFIG:
+  type: git
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/centos-65-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+  centos-65-x64:
+    roles:
+      - master
+    platform: el-6-x86_64
+    box : centos-65-x64-vbox436-nocm
+    box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box
+    hypervisor : vagrant
+CONFIG:
+  type: foss
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/debian-607-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+  debian-607-x64.local:
+    roles:
+      - master
+    platform: debian-6-amd64
+    box : debian-607-x64-vbox4210-nocm
+    box_url : http://puppet-vagrant-boxes.puppetlabs.com/debian-607-x64-vbox4210-nocm.box
+    hypervisor : vagrant
+CONFIG:
+  type: git
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/debian-73-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+  debian-73-x64.local:
+    roles:
+      - master
+    platform: debian-7-amd64
+    box : debian-73-x64-virtualbox-nocm
+    box_url : http://puppet-vagrant-boxes.puppetlabs.com/debian-73-x64-virtualbox-nocm.box
+    hypervisor : vagrant
+CONFIG:
+  type: git
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/default.yml
@@ -0,0 +1,10 @@
+HOSTS:
+  centos-64-x64.local:
+    roles:
+      - master
+    platform: el-6-x86_64
+    box : centos-64-x64-vbox4210-nocm
+    box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
+    hypervisor : vagrant
+CONFIG:
+  type: git
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+  ubuntu-server-10044-x64:
+    roles:
+      - master
+    platform: ubuntu-10.04-amd64
+    box : ubuntu-server-10044-x64-vbox4210-nocm
+    box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-10044-x64-vbox4210-nocm.box
+    hypervisor : vagrant
+CONFIG:
+  type: foss
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+  ubuntu-server-12042-x64:
+    roles:
+      - master
+    platform: ubuntu-12.04-amd64
+    box : ubuntu-server-12042-x64-vbox4210-nocm
+    box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210-nocm.box
+    hypervisor : vagrant
+CONFIG:
+  type: foss
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml
@@ -0,0 +1,11 @@
+HOSTS:
+  ubuntu-server-1404-x64:
+    roles:
+      - master
+    platform: ubuntu-14.04-amd64
+    box : puppetlabs/ubuntu-14.04-64-nocm
+    box_url : https://vagrantcloud.com/puppetlabs/ubuntu-14.04-64-nocm
+    hypervisor : vagrant
+CONFIG:
+  log_level   : debug
+  type: git
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/ubuntu-server-14042-x64-vcloud.yml
@@ -0,0 +1,17 @@
+HOSTS:
+  ubuntu1204:
+    roles:
+      - default
+      - agent
+    platform: ubuntu-12.04-amd64
+    template: ubuntu-1204-x86_64
+    hypervisor: vcloud
+CONFIG:
+  type: git
+  nfs_server: none
+  consoleport: 443
+  datastore: instance0
+  folder: Delivery/Quality Assurance/Enterprise/Dynamic
+  resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+  pooling_api: http://vcloud.delivery.puppetlabs.net/
+  pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/nodesets/windows-2012r2-64a.yml
@@ -0,0 +1,17 @@
+HOSTS:
+  v1bo8bpae8lkmsu:
+    roles:
+      - agent
+      - default
+    platform: windows-2012r2-x86_64
+    template: win-2012r2-x86_64
+    hypervisor: vcloud
+CONFIG:
+  type: git
+  nfs_server: none
+  consoleport: 443
+  datastore: instance0
+  folder: Delivery/Quality Assurance/FOSS/Dynamic
+  resourcepool: delivery/Quality Assurance/FOSS/Dynamic
+  pooling_api: http://vcloud.delivery.puppetlabs.net/
+  pe_dir: http://enterprise.delivery.puppetlabs.net/archives/releases/3.3.2/
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/private_key_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper_acceptance'
+
+hostname = default.node_name
+
+describe 'managing java private keys', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+  include_context 'common variables'
+  case fact('osfamily')
+    when 'windows'
+      target = 'c:/private_key.ts'
+    else
+      target = '/etc/private_key.ts'
+  end
+
+  it 'creates a private key' do
+    pp = <<-EOS
+      java_ks { 'broker.example.com:#{target}':
+        ensure       => #{@ensure_ks},
+        certificate  => "#{@temp_dir}ca.pem",
+        private_key  => "#{@temp_dir}privkey.pem",
+        password     => 'puppet',
+        path         => #{@resource_path},
+      }
+    EOS
+
+    apply_manifest(pp, :catch_failures => true)
+  end
+
+  it 'verifies the private key' do
+    shell("#{@keytool_path}keytool -list -v -keystore #{target} -storepass puppet") do |r|
+      expect(r.exit_code).to be_zero
+      expect(r.stdout).to match(/Alias name: broker\.example\.com/)
+      expect(r.stdout).to match(/Entry type: (keyEntry|PrivateKeyEntry)/)
+      expect(r.stdout).to match(/CN=Test CA/)
+    end
+  end
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/truststore_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper_acceptance'
+
+describe 'managing java truststores', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+  include_context 'common variables'
+
+  case fact('osfamily')
+    when "windows"
+      target = 'c:/truststore.ts'
+    else
+      target = '/etc/truststore.ts'
+  end
+
+  it 'creates a truststore' do
+    pp = <<-EOS
+      java_ks { 'puppetca:truststore':
+        ensure       => #{@ensure_ks},
+        certificate  => "#{@temp_dir}ca.pem",
+        target       => '#{target}',
+        password     => 'puppet',
+        trustcacerts => true,
+        path         => #{@resource_path},
+    }
+    EOS
+    apply_manifest(pp, :catch_failures => true)
+  end
+
+  it 'verifies the truststore' do
+    shell("#{@keytool_path}keytool -list -v -keystore #{target} -storepass puppet") do |r|
+      expect(r.exit_code).to be_zero
+      expect(r.stdout).to match(/Your keystore contains 1 entry/)
+      expect(r.stdout).to match(/Alias name: puppetca/)
+      expect(r.stdout).to match(/CN=Test CA/)
+    end
+  end
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/acceptance/unsupported_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper_acceptance'
+
+describe 'unsupported distributions and OSes', :if => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+  case fact('osfamily')
+  when "Solaris"
+    keytool_path = '/usr/java/bin/'
+    resource_path = "['/usr/java/bin/','/opt/puppet/bin/','/usr/bin/']"
+  when "AIX"
+    keytool_path = '/usr/java6/bin/'
+    resource_path = "['/usr/java6/bin/','/opt/puppet/bin/']"
+  else
+    resource_path = "undef"
+  end
+  it 'should fail' do
+    pp = <<-EOS
+    java_ks { 'puppetca:keystore':
+      ensure       => latest,
+      certificate  => "/tmp/ca.pem",
+      target       => '/etc/keystore.ks',
+      password     => 'puppet',
+      trustcacerts => true,
+      path         => #{resource_path},
+    }
+    EOS
+    expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/unsupported os/)
+  end
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/spec_helper.rb
@@ -0,0 +1,7 @@
+require 'puppetlabs_spec_helper/module_spec_helper'
+
+# put local configuration and setup into spec_helper_local
+begin
+  require 'spec_helper_local'
+rescue LoadError
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/spec_helper_acceptance.rb
@@ -0,0 +1,136 @@
+require 'beaker-rspec/spec_helper'
+require 'beaker-rspec/helpers/serverspec'
+require 'beaker/puppet_install_helper'
+
+run_puppet_install_helper
+install_ca_certs_on default if default['platform'] =~ /windows/i
+
+UNSUPPORTED_PLATFORMS = []
+
+def create_keys_for_test(host)
+  # Generate private key and CA for keystore
+  if host['platform'] =~ /windows/i
+    temp_dir = 'C:\\tmp\\'
+    on host, 'mkdir /cygdrive/c/tmp'
+  else
+    temp_dir = '/tmp/'
+  end
+
+  create_certs(host, temp_dir)
+end
+
+def create_certs(host, tmpdir)
+  require 'openssl'
+  key = OpenSSL::PKey::RSA.new 1024
+  ca = OpenSSL::X509::Certificate.new
+  ca.serial = 1
+  ca.public_key = key.public_key
+  subj = '/CN=Test CA/ST=Denial/L=Springfield/O=Dis/CN=www.example.com'
+  ca.subject = OpenSSL::X509::Name.parse subj
+  ca.issuer = ca.subject
+  ca.not_before = Time.now
+  ca.not_after = ca.not_before + 360
+  ca.sign(key, OpenSSL::Digest::SHA256.new)
+
+  key2 = OpenSSL::PKey::RSA.new 1024
+  ca2 = OpenSSL::X509::Certificate.new
+  ca2.serial = 2
+  ca2.public_key = key2.public_key
+  subj2 = '/CN=Test CA/ST=Denial/L=Springfield/O=Dis/CN=www.example.com'
+  ca2.subject = OpenSSL::X509::Name.parse subj2
+  ca2.issuer = ca2.subject
+  ca2.not_before = Time.now
+  ca2.not_after = ca2.not_before + 360
+  ca2.sign(key2, OpenSSL::Digest::SHA256.new)
+
+  key_chain = OpenSSL::PKey::RSA.new 1024
+  chain = OpenSSL::X509::Certificate.new
+  chain.serial = 3
+  chain.public_key = key_chain.public_key
+  chain_subj = '/CN=Chain CA/ST=Denial/L=Springfield/O=Dis/CN=www.example.net'
+  chain.subject = OpenSSL::X509::Name.parse chain_subj
+  chain.issuer = ca.subject
+  chain.not_before = Time.now
+  chain.not_after = chain.not_before + 360
+  chain.sign(key, OpenSSL::Digest::SHA256.new)
+
+  key_chain2 = OpenSSL::PKey::RSA.new 1024
+  chain2 = OpenSSL::X509::Certificate.new
+  chain2.serial = 4
+  chain2.public_key = key_chain2.public_key
+  chain2_subj = '/CN=Chain CA 2/ST=Denial/L=Springfield/O=Dis/CN=www.example.net'
+  chain2.subject = OpenSSL::X509::Name.parse chain2_subj
+  chain2.issuer = chain.subject
+  chain2.not_before = Time.now
+  chain2.not_after = chain2.not_before + 360
+  chain2.sign(key_chain, OpenSSL::Digest::SHA256.new)
+
+  key_leaf = OpenSSL::PKey::RSA.new 1024
+  leaf = OpenSSL::X509::Certificate.new
+  leaf.serial = 5
+  leaf.public_key = key_leaf.public_key
+  leaf_subj = '/CN=Leaf Cert/ST=Denial/L=Springfield/O=Dis/CN=www.example.net'
+  leaf.subject = OpenSSL::X509::Name.parse leaf_subj
+  leaf.issuer = chain2.subject
+  leaf.not_before = Time.now
+  leaf.not_after = leaf.not_before + 360
+  leaf.sign(key_chain2, OpenSSL::Digest::SHA256.new)
+
+  create_remote_file(host, "#{tmpdir}/privkey.pem", key.to_pem)
+  create_remote_file(host, "#{tmpdir}/ca.pem", ca.to_pem)
+  create_remote_file(host, "#{tmpdir}/ca2.pem", ca2.to_pem)
+  create_remote_file(host, "#{tmpdir}/chain.pem", chain2.to_pem + chain.to_pem)
+  create_remote_file(host, "#{tmpdir}/leafkey.pem", key_leaf.to_pem)
+  create_remote_file(host, "#{tmpdir}/leaf.pem", leaf.to_pem)
+  create_remote_file(host, "#{tmpdir}/leafchain.pem", leaf.to_pem + chain2.to_pem + chain.to_pem)
+end
+
+
+RSpec.configure do |c|
+  # Project root
+  proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+
+  # Readable test descriptions
+  c.formatter = :documentation
+
+  # Configure all nodes in nodeset
+  c.before :suite do
+    # Install module and dependencies
+    hosts.each do |host|
+      create_keys_for_test(host)
+      copy_module_to(host, :source => proj_root, :module_name => 'java_ks')
+      #install java if windows
+      if host['platform'] =~ /windows/i
+        on host, puppet('module install cyberious-windows_java')
+      else
+        on host, puppet('module', 'install', 'puppetlabs-java'), {:acceptable_exit_codes => [0, 1]}
+      end
+    end
+  end
+end
+
+RSpec.shared_context 'common variables' do
+  before {
+    java_major, java_minor = (ENV['JAVA_VERSION'] || '7u67').split('u')
+    @ensure_ks = 'latest'
+    @temp_dir = '/tmp/'
+    @resource_path = "undef"
+    @target = '/etc/truststore.ts'
+    case fact('osfamily')
+      when "Solaris"
+        @keytool_path = '/usr/java/bin/'
+        @resource_path = "['/usr/java/bin/','/opt/puppet/bin/']"
+        @target = '/etc/truststore.ts'
+      when "AIX"
+        @keytool_path = '/usr/java6/bin/'
+        @resource_path = "['/usr/java6/bin/','/usr/bin/']"
+        @target = '/etc/truststore.ts'
+      when 'windows'
+        @ensure_ks = 'present'
+        @keytool_path = "C:/Java/jdk1.#{java_major}.0_#{java_minor}/bin/"
+        @target = 'c:/truststore.ts'
+        @temp_dir = 'C:/tmp/'
+        @resource_path = "['C:/Java/jdk1.#{java_major}.0_#{java_minor}/bin/']"
+    end
+  }
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/unit/puppet/provider/java_ks/keytool_spec.rb
@@ -0,0 +1,180 @@
+#!/usr/bin/env rspec
+require 'spec_helper'
+
+describe Puppet::Type.type(:java_ks).provider(:keytool) do
+
+  let(:params) do
+    {
+      :title       => 'app.example.com:/tmp/application.jks',
+      :name        => 'app.example.com',
+      :target      => '/tmp/application.jks',
+      :password    => 'puppet',
+      :certificate => '/tmp/app.example.com.pem',
+      :private_key => '/tmp/private/app.example.com.pem',
+      :storetype   => 'jceks',
+      :provider    => described_class.name
+    }
+  end
+
+  let(:resource) do
+    Puppet::Type.type(:java_ks).new(params)
+  end
+
+  let(:provider) do
+    resource.provider
+  end
+
+  before do
+    provider.stubs(:command).with(:keytool).returns('mykeytool')
+    provider.stubs(:command).with(:openssl).returns('myopenssl')
+
+    provider.stubs(:command_keytool).returns 'mykeytool'
+    provider.stubs(:command_openssl).returns 'myopenssl'
+
+    tempfile = stub('tempfile', :class => Tempfile,
+                :write => true,
+                :flush => true,
+                :close! => true,
+                :path => '/tmp/testing.stuff'
+               )
+    Tempfile.stubs(:new).returns(tempfile)
+  end
+
+  describe 'when updating a certificate' do
+    it 'should call destroy and create' do
+      provider.expects(:destroy)
+      provider.expects(:create)
+      provider.update
+    end
+  end
+
+  describe 'when running keystore commands' do
+    it 'should call the passed command' do
+      cmd = '/bin/echo testing 1 2 3'
+
+      if Puppet::Util::Execution.respond_to?(:execute)
+        exec_class = Puppet::Util::Execution
+      else
+        exec_class = Puppet::Util
+      end
+      exec_class.expects(:execute).with(
+        cmd,
+        :failonfail => true,
+        :combine    => true
+      )
+      provider.run_command(cmd)
+    end
+  end
+
+  describe 'when importing a private key and certifcate' do
+    describe '#to_pkcs12' do
+      it 'converts a certificate to a pkcs12 file' do
+        testing_key = OpenSSL::PKey::RSA.new 1024
+        testing_ca = OpenSSL::X509::Certificate.new
+        testing_ca.serial = 1
+        testing_ca.public_key = testing_key.public_key
+        testing_subj = '/CN=Test CA/ST=Denial/L=Springfield/O=Dis/CN=www.example.com'
+        testing_ca.subject = OpenSSL::X509::Name.parse testing_subj
+        testing_ca.issuer = testing_ca.subject
+        testing_ca.not_before = Time.now
+        testing_ca.not_after = testing_ca.not_before + 360
+        testing_ca.sign(testing_key, OpenSSL::Digest::SHA256.new)
+
+        provider.stubs(:get_password).returns(resource[:password])
+        File.stubs(:read).with(resource[:private_key]).returns('private key')
+        File.stubs(:read).with(resource[:certificate]).returns(testing_ca.to_pem)
+        OpenSSL::PKey::RSA.expects(:new).with('private key').returns('priv_obj')
+        OpenSSL::X509::Certificate.expects(:new).with(testing_ca.to_pem.chomp).returns('cert_obj')
+
+        pkcs_double = BogusPkcs.new()
+        pkcs_double.expects(:to_der)
+        OpenSSL::PKCS12.expects(:create).with(resource[:password],resource[:name],'priv_obj','cert_obj',[]).returns(pkcs_double)
+        provider.to_pkcs12('/tmp/testing.stuff')
+      end
+    end
+
+    describe "#import_ks" do
+      it 'should execute openssl and keytool with specific options' do
+        provider.expects(:to_pkcs12).with('/tmp/testing.stuff')
+        provider.expects(:run_command).with([
+            'mykeytool', '-importkeystore', '-srcstoretype', 'PKCS12',
+            '-destkeystore', resource[:target],
+            '-srckeystore', '/tmp/testing.stuff',
+            '-alias', resource[:name],
+          ], any_parameters
+        )
+        provider.import_ks
+      end
+
+      it 'should use destkeypass when provided' do
+        dkp = resource.dup
+        dkp[:destkeypass] = 'keypass'
+        provider.expects(:to_pkcs12).with('/tmp/testing.stuff')
+        provider.expects(:run_command).with([
+            'mykeytool', '-importkeystore', '-srcstoretype', 'PKCS12',
+            '-destkeystore', dkp[:target],
+            '-srckeystore', '/tmp/testing.stuff',
+            '-alias', dkp[:name], '-destkeypass', dkp[:destkeypass]
+          ], any_parameters
+        )
+        provider.import_ks
+      end
+    end
+  end
+
+  describe 'when creating entires in a keystore' do
+    let(:params) do
+      {
+        :title       => 'app.example.com:/tmp/application.jks',
+        :name        => 'app.example.com',
+        :target      => '/tmp/application.jks',
+        :password    => 'puppet',
+        :certificate => '/tmp/app.example.com.pem',
+        :private_key => '/tmp/private/app.example.com.pem',
+        :provider    => described_class.name
+      }
+    end
+
+    let(:resource) do
+      Puppet::Type.type(:java_ks).new(params)
+    end
+
+    let(:provider) do
+      resource.provider
+    end
+    it 'should call import_ks if private_key and certificate are provided' do
+      provider.expects(:import_ks)
+      provider.create
+    end
+
+    it 'should call keytool with specific options if only certificate is provided' do
+      no_pk = resource.dup
+      no_pk.delete(:private_key)
+      provider.expects(:run_command).with([
+          'mykeytool', '-importcert', '-noprompt',
+          '-alias', no_pk[:name],
+          '-file', no_pk[:certificate],
+          '-keystore', no_pk[:target],
+        ], any_parameters
+      )
+      no_pk.provider.expects(:import_ks).never
+      no_pk.provider.create
+    end
+  end
+
+  describe 'when removing entries from keytool' do
+    it 'should execute keytool with a specific set of options' do
+      provider.expects(:run_command).with([
+          'mykeytool', '-delete',
+          '-alias', resource[:name],
+          '-keystore', resource[:target]
+        ], any_parameters
+      )
+      provider.destroy
+    end
+  end
+end
+
+class BogusPkcs
+
+end
new file mode 100644
--- /dev/null
+++ b/modules/java_ks/spec/unit/puppet/type/java_ks_spec.rb
@@ -0,0 +1,182 @@
+!#/usr/bin/env rspec
+require 'spec_helper'
+
+describe Puppet::Type.type(:java_ks) do
+
+  before do
+    @app_example_com = {
+      :title       => 'app.example.com:/tmp/application.jks',
+      :name        => 'app.example.com',
+      :target      => '/tmp/application.jks',
+      :password    => 'puppet',
+      :destkeypass => 'keypass',
+      :certificate => '/tmp/app.example.com.pem',
+      :private_key => '/tmp/private/app.example.com.pem',
+      :storetype   => 'jceks',
+      :provider    => :keytool
+    }
+    @provider = stub('provider', :class => Puppet::Type.type(:java_ks).defaultprovider, :clear => nil)
+    Puppet::Type.type(:java_ks).defaultprovider.stubs(:new).returns(@provider)
+  end
+
+  let(:jks_resource) do
+    @app_example_com
+  end
+
+  it 'should default to being present' do
+    expect(Puppet::Type.type(:java_ks).new(@app_example_com)[:ensure]).to eq(:present)
+  end
+
+  describe 'when validating attributes' do
+
+    [:name, :target, :private_key, :certificate, :password, :password_file, :trustcacerts, :destkeypass].each do |param|
+      it "should have a #{param} parameter" do
+        expect(Puppet::Type.type(:java_ks).attrtype(param)).to eq(:param)
+      end
+    end
+
+    [:ensure].each do |prop|
+      it "should have a #{prop} property" do
+        expect(Puppet::Type.type(:java_ks).attrtype(prop)).to eq(:property)
+      end
+    end
+  end
+
+  describe 'when validating attribute values' do
+
+    [:present, :absent, :latest].each do |value|
+      it "should support #{value} as a value to ensure" do
+        Puppet::Type.type(:java_ks).new(jks_resource.merge({ :ensure => value }))
+      end
+    end
+
+    it "first half of title should map to name parameter" do
+      jks = jks_resource.dup
+      jks.delete(:name)
+      expect(Puppet::Type.type(:java_ks).new(jks)[:name]).to eq(jks_resource[:name])
+    end
+
+    it "second half of title should map to target parameter when no target is supplied" do
+      jks = jks_resource.dup
+      jks.delete(:target)
+      expect(Puppet::Type.type(:java_ks).new(jks)[:target]).to eq(jks_resource[:target])
+    end
+
+    it "second half of title should not map to target parameter when target is supplied" do
+      jks = jks_resource.dup
+      jks[:target] = '/tmp/some_other_app.jks'
+      expect(Puppet::Type.type(:java_ks).new(jks)[:target]).not_to eq(jks_resource[:target])
+      expect(Puppet::Type.type(:java_ks).new(jks)[:target]).to eq('/tmp/some_other_app.jks')
+    end
+
+    it 'title components should map to namevar parameters' do
+      jks = jks_resource.dup
+      jks.delete(:name)
+      jks.delete(:target)
+      expect(Puppet::Type.type(:java_ks).new(jks)[:name]).to eq(jks_resource[:name])
+      expect(Puppet::Type.type(:java_ks).new(jks)[:target]).to eq(jks_resource[:target])
+    end
+
+    it 'should downcase :name values' do
+      jks = jks_resource.dup
+      jks[:name] = 'APP.EXAMPLE.COM'
+      expect(Puppet::Type.type(:java_ks).new(jks)[:name]).to eq(jks_resource[:name])
+    end
+ 
+    it 'should have :false value to :trustcacerts when parameter not provided' do
+      expect(Puppet::Type.type(:java_ks).new(jks_resource)[:trustcacerts]).to eq(:false)
+    end
+
+    it 'should fail if both :password and :password_file are provided' do
+      jks = jks_resource.dup
+      jks[:password_file] = '/path/to/password_file'
+      expect {
+        Puppet::Type.type(:java_ks).new(jks)
+      }.to raise_error(Puppet::Error, /You must pass either/)
+    end
+
+    it 'should fail if neither :password or :password_file is provided' do
+      jks = jks_resource.dup
+      jks.delete(:password)
+      expect {
+        Puppet::Type.type(:java_ks).new(jks)
+      }.to raise_error(Puppet::Error, /You must pass one of/)
+    end
+
+    it 'should fail if :password is fewer than 6 characters' do
+      jks = jks_resource.dup
+      jks[:password] = 'aoeui'
+      expect {
+        Puppet::Type.type(:java_ks).new(jks)
+      }.to raise_error(Puppet::Error, /6 characters/)
+    end
+
+    it 'should fail if :destkeypass is fewer than 6 characters' do
+      jks = jks_resource.dup
+      jks[:destkeypass] = 'aoeui'
+      expect {
+        Puppet::Type.type(:java_ks).new(jks)
+      }.to raise_error(Puppet::Error, /length 6/)
+    end
+
+  end
+
+  describe 'when ensure is set to latest' do
+    it 'insync? should return false if md5 fingerprints do not match and state is :present' do
+      jks = jks_resource.dup
+      jks[:ensure] = :latest
+      @provider.stubs(:latest).returns('AF:61:1C:FF:C7:C0:B2:C6:37:C5:D1:6E:00:AB:7A:B2')
+      @provider.stubs(:current).returns('B4:54:EB:55:86:41:84:2E:22:A0:6A:36:1B:28:47:76')
+      expect(Puppet::Type.type(:java_ks).new(jks).property(:ensure).insync?(:present)).to be_falsey
+    end
+
+    it 'insync? should return false if state is :absent' do
+      jks = jks_resource.dup
+      jks[:ensure] = :latest
+      expect(Puppet::Type.type(:java_ks).new(jks).property(:ensure).insync?(:absent)).to be_falsey
+    end
+
+    it 'insync? should return true if md5 fingerprints match and state is :present' do
+      jks = jks_resource.dup
+      jks[:ensure] = :latest
+      @provider.stubs(:latest).returns('AF:61:1C:FF:C7:C0:B2:C6:37:C5:D1:6E:00:AB:7A:B2')
+      @provider.stubs(:current).returns('AF:61:1C:FF:C7:C0:B2:C6:37:C5:D1:6E:00:AB:7A:B2')
+      expect(Puppet::Type.type(:java_ks).new(jks).property(:ensure).insync?(:present)).to be_truthy
+    end
+  end
+
+  describe 'when file resources are in the catalog' do
+    before do
+      @file_provider = stub('provider', :class => Puppet::Type.type(:file).defaultprovider, :clear => nil)
+      Puppet::Type.type(:file).defaultprovider.stubs(:new).returns(@file_provider)
+    end
+
+    [:private_key, :certificate].each do |file|
+      it "should autorequire for #{file}" do
+        test_jks = Puppet::Type.type(:java_ks).new(jks_resource)
+        test_file = Puppet::Type.type(:file).new({:title => jks_resource[file]})
+
+        config = Puppet::Resource::Catalog.new :testing do |conf|
+          [test_jks, test_file].each do |resource| conf.add_resource resource end
+        end
+
+        rel = test_jks.autorequire[0]
+        expect(rel.source.ref).to eq(test_file.ref)
+        expect(rel.target.ref).to eq(test_jks.ref)
+      end
+    end
+
+    it 'should autorequire for the :target directory' do
+      test_jks = Puppet::Type.type(:java_ks).new(jks_resource)
+      test_file = Puppet::Type.type(:file).new({:title => ::File.dirname(jks_resource[:target])})
+
+      config = Puppet::Resource::Catalog.new :testing do |conf|
+        [test_jks, test_file].each do |resource| conf.add_resource resource end
+      end
+
+      rel = test_jks.autorequire[0]
+      expect(rel.source.ref).to eq(test_file.ref)
+      expect(rel.target.ref).to eq(test_jks.ref)
+    end
+  end
+end
new file mode 100644
--- /dev/null
+++ b/modules/packages/manifests/jdk17.pp
@@ -0,0 +1,23 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+class packages::jdk17 {
+
+  anchor {
+        'packages::jdk17::begin': ;
+        'packages::jdk17::end': ;
+    }
+
+    case $::operatingsystem {
+        CentOS: {
+            package {
+                'java-1.7.0-openjdk-devel':
+                    ensure => 'present';
+            }
+        }
+        default: {
+            fail("cannot install on $::operatingsystem")
+        }
+    }
+
+}
new file mode 100644
--- /dev/null
+++ b/modules/pushapkworker/manifests/init.pp
@@ -0,0 +1,95 @@
+class pushapkworker {
+    include ::config
+    include pushapkworker::services
+    include pushapkworker::settings
+    include dirs::builds
+    include packages::mozilla::python35
+    include users::builder
+    include tweaks::swap_on_instance_storage
+    include packages::gcc
+    include packages::make
+    include packages::libffi
+    include pushapkworker::jarsigner_init
+    include pushapkworker::mime_types
+
+    $env_config = $config::pushapk_scriptworker_env_config[$pushapkworker_env]
+    $google_play_config = $env_config['google_play_config']
+
+    python35::virtualenv {
+        $pushapkworker::settings::root:
+            python3  => $packages::mozilla::python35::python3,
+            require  => Class['packages::mozilla::python35'],
+            user     => $users::builder::username,
+            group    => $users::builder::group,
+            mode     => 700,
+            packages => [
+                'aiohttp==1.0.2',
+                'arrow==0.8.0',
+                'async-timeout==1.0.0',
+                'cffi==1.8.3',
+                'chardet==2.3.0',
+                'cryptography==1.5.2',
+                'defusedxml==0.4.1',
+                'frozendict==1.0',
+                'google-api-python-client==1.5.3',
+                'httplib2==0.9.2',
+                'idna==2.1',
+                'jsonschema==2.5.1',
+                'mohawk==0.3.3',
+                'mozapkpublisher==0.1.3',
+                'multidict==2.1.2',
+                'oauth2client==3.0.0',
+                'pexpect==4.2.1',
+                'ptyprocess==0.5.1',
+                'pushapkscript==0.1.4',
+                'pyasn1==0.1.9',
+                'pyasn1-modules==0.0.8',
+                'pycparser==2.14',
+                'pyOpenSSL==16.1.0',
+                'python-dateutil==2.5.3',
+                'python-gnupg==0.3.9',
+                'requests==2.11.1',
+                'rsa==3.4.2',
+                'scriptworker==0.7.2',
+                'simplejson==3.8.2',
+                'six==1.10.0',
+                'slugid==1.0.7',
+                'taskcluster==0.3.4',
+                'uritemplate==0.6',
+                'virtualenv==15.0.3'
+            ];
+    }
+
+    nrpe::custom {
+        'pushapkworker.cfg':
+            content => template("${module_name}/nagios.cfg.erb");
+    }
+
+    File {
+        ensure      => present,
+        mode        => 600,
+        owner       => $users::builder::username,
+        group       => $users::builder::group,
+        show_diff   => false,
+    }
+
+    file {
+        $config::pushapk_scriptworker_script_config:
+            require     => Python35::Virtualenv[$pushapkworker::settings::root],
+            content     => template("${module_name}/script_config.json.erb"),
+            show_diff   => true;
+
+        $config::pushapk_scriptworker_worker_config:
+            require     => Python35::Virtualenv[$pushapkworker::settings::root],
+            content     => template("${module_name}/config.json.erb");
+
+        $google_play_config['aurora']['certificate_target_location']:
+            content     => $google_play_config['aurora']['certificate'];
+
+        $google_play_config['beta']['certificate_target_location']:
+            content     => $google_play_config['beta']['certificate'];
+
+        $google_play_config['release']['certificate_target_location']:
+            content     => $google_play_config['release']['certificate'];
+    }
+}
new file mode 100644
--- /dev/null
+++ b/modules/pushapkworker/manifests/jarsigner_init.pp
@@ -0,0 +1,35 @@
+class pushapkworker::jarsigner_init {
+    include ::config
+    include packages::jdk17
+
+    $nightly = "${pushapkworker::settings::root}/nightly.cer"
+    $release = "${pushapkworker::settings::root}/release.cer"
+
+    file {
+        $nightly:
+            ensure      => 'present',
+            content     => secret('pushapk_scriptworker_nightly_jarsigner_certificate'),
+            show_diff   => false;
+
+        $release:
+            ensure      => 'present',
+            content     => secret('pushapk_scriptworker_release_jarsigner_certificate'),
+            show_diff   => false;
+    }
+
+    java_ks {
+        $config::pushapk_scriptworker_jarsigner_nightly_certificate_alias:
+            ensure       => latest,
+            certificate  => $nightly,
+            target       => $config::pushapk_scriptworker_jarsigner_keystore,
+            password     => secret('pushapk_scriptworker_jarsigner_keystore_password'),
+            trustcacerts => true;
+
+        $config::pushapk_scriptworker_jarsigner_release_certificate_alias:
+            ensure       => latest,
+            certificate  => $release,
+            target       => $config::pushapk_scriptworker_jarsigner_keystore,
+            password     => secret('pushapk_scriptworker_jarsigner_keystore_password'),
+            trustcacerts => true;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/modules/pushapkworker/manifests/mime_types.pp
@@ -0,0 +1,17 @@
+class pushapkworker::mime_types {
+
+    case $::operatingsystem {
+        # This file is used by google-api-python-client to make sure it's pushing an APK. It relies on
+        # https://docs.python.org/3/library/mimetypes.html which needs this file, no matter what disto
+        # we're on. Without it, google-api-python-client refuses to handle files.
+        CentOS: {
+            file { '/etc/mime.types':
+                mode        => '0644',
+                content     => 'application/vnd.android.package-archive     apk',
+            }
+        }
+        default: {
+            fail("cannot install on ${::operatingsystem}")
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/modules/pushapkworker/manifests/services.pp
@@ -0,0 +1,22 @@
+class pushapkworker::services {
+    include ::config
+    include pushapkworker::settings
+    include packages::mozilla::supervisor
+
+    supervisord::supervise {
+        'pushapkworker':
+            command      => "${pushapkworker::settings::root}/bin/scriptworker ${config::pushapk_scriptworker_worker_config}",
+            user         => $::config::builder_username,
+            require      => [ File[$config::pushapk_scriptworker_worker_config],
+                              File[$config::pushapk_scriptworker_script_config]],
+            extra_config => template("${module_name}/supervisor_config.erb");
+    }
+    exec {
+        'restart-pushapkworker':
+            command     => '/usr/bin/supervisorctl restart pushapkworker',
+            refreshonly => true,
+            subscribe   => [Python35::Virtualenv[$pushapkworker::settings::root],
+                            File[$config::pushapk_scriptworker_worker_config],
+                            File[$config::pushapk_scriptworker_script_config]];
+    }
+}
new file mode 100644
--- /dev/null
+++ b/modules/pushapkworker/manifests/settings.pp
@@ -0,0 +1,5 @@
+class pushapkworker::settings {
+    include ::config
+
+    $root = $config::pushapk_scriptworker_root
+}
new file mode 100644
--- /dev/null
+++ b/modules/pushapkworker/templates/config.json.erb
@@ -0,0 +1,30 @@
+{
+    "provisioner_id": "<%= @env_config['provisioner_id'] %>",
+    "worker_group": "<%= @env_config['worker_group'] %>",
+    "worker_type": "<%= @env_config['worker_type'] %>",
+<% if @env_config['worker_id'] %>
+    "worker_id": "<%= @env_config['worker_id'] %>",
+<% else %>
+    "worker_id": "<%= @hostname %>",
+<% end %>
+
+    "work_dir": "<%= scope.lookupvar('config::pushapk_scriptworker_root') %>/work",
+    "log_dir": "<%= scope.lookupvar('config::pushapk_scriptworker_root') %>/logs",
+    "artifact_dir": "<%= scope.lookupvar('config::pushapk_scriptworker_root') %>/artifacts",
+    "task_log_dir": "<%= scope.lookupvar('config::pushapk_scriptworker_root') %>/artifacts/public/logs",
+    "valid_artifact_path_regexes": ["^/v1/task/(?P<taskId>[^/]+)(/runs/\\d+)?/artifacts/(?P<filepath>.*)$"],
+    "verify_chain_of_trust": false,
+    "sign_chain_of_trust": false,
+
+    "credentials": {
+        "clientId": "<%= @env_config['taskcluster_client_id'] %>",
+        "accessToken": "<%= @env_config['taskcluster_access_token'] %>"
+    },
+
+    "artifact_expiration_hours": <%= scope.lookupvar('config::pushapk_scriptworker_artifact_expiration_hours') %>,
+    "artifact_upload_timeout": <%= scope.lookupvar('config::pushapk_scriptworker_artifact_upload_timeout') %>,
+    "task_script": ["<%= scope.lookupvar('config::pushapk_scriptworker_root') %>/bin/pushapkscript", "<%= scope.lookupvar('config::pushapk_scriptworker_script_config') %>" ],
+    "task_max_timeout": <%= scope.lookupvar('config::pushapk_scriptworker_task_max_timeout') %>,
+
+    "verbose": <%= @env_config['verbose_logging'] %>
+}
new file mode 100644
--- /dev/null
+++ b/modules/pushapkworker/templates/nagios.cfg.erb
@@ -0,0 +1,1 @@
+command[check_pushapkworker]=<%= scope.lookupvar('nrpe::base::plugins_dir') %>/check_procs -c 1:1 -C pushapkworker
new file mode 100644
--- /dev/null
+++ b/modules/pushapkworker/templates/script_config.json.erb
@@ -0,0 +1,27 @@
+{
+    "work_dir": "<%= scope.lookupvar('config::pushapk_scriptworker_root') %>/work",
+    "schema_file": "<%= scope.lookupvar('config::pushapk_scriptworker_root') %>/lib/python3.5/site-packages/pushapkscript/data/pushapk_task_schema.json",
+    "verbose": <%= @env_config['verbose_logging'] %>,
+
+    "google_play_accounts": {
+        "aurora": {
+            "service_account": "<%= @google_play_config['aurora']['service_account'] %>",
+            "certificate": "<%= @google_play_config['aurora']['certificate_target_location'] %>"
+        },
+        "beta": {
+            "service_account": "<%= @google_play_config['beta']['service_account'] %>",
+            "certificate": "<%= @google_play_config['beta']['certificate_target_location'] %>"
+        },
+        "release": {
+            "service_account": "<%= @google_play_config['release']['service_account'] %>",
+            "certificate": "<%= @google_play_config['release']['certificate_target_location'] %>"
+        }
+    },
+
+    "jarsigner_key_store": "<%= scope.lookupvar('config::pushapk_scriptworker_jarsigner_keystore') %>",
+    "jarsigner_certificate_aliases": {
+        "aurora": "<%= scope.lookupvar('config::pushapk_scriptworker_jarsigner_nightly_certificate_alias') %>",
+        "beta": "<%= scope.lookupvar('config::pushapk_scriptworker_jarsigner_nightly_certificate_alias') %>",
+        "release": "<%= scope.lookupvar('config::pushapk_scriptworker_jarsigner_release_certificate_alias') %>"
+    }
+}
new file mode 100644
--- /dev/null
+++ b/modules/pushapkworker/templates/supervisor_config.erb
@@ -0,0 +1,6 @@
+log_stderr=true
+log_stdout=true
+redirect_stderr=true
+stdout_logfile=syslog
+autorestart=true
+autostart=true
new file mode 100644
--- /dev/null
+++ b/modules/toplevel/manifests/server/pushapkworker.pp
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+class toplevel::server::pushapkworker inherits toplevel::server {
+    include ::pushapkworker
+}