├── CONTRIBUTORS ├── .rspec ├── spec ├── spec.opts ├── fixtures │ └── modules │ │ └── .gitignore ├── spec_helper.rb └── unit │ ├── provider │ └── git_webhook_github_spec.rb │ └── type │ └── git_webhook_spec.rb ├── github_new_token.png ├── .gitignore ├── lib ├── puppet_x │ ├── puppetlabs │ │ ├── property │ │ │ └── read_only.rb │ │ └── gms.rb │ └── gms │ │ ├── provider.rb │ │ └── type.rb └── puppet │ ├── type │ ├── git_deploy_key.rb │ ├── git_groupteam.rb │ ├── git_webhook.rb │ ├── git_groupteam_member.rb │ └── gms_webhook.rb │ └── provider │ ├── git_deploy_key │ ├── github.rb │ ├── gitlab.rb │ └── stash.rb │ ├── git_groupteam │ └── gitlab.rb │ ├── git_webhook │ ├── github.rb │ ├── gitlab.rb │ └── stash.rb │ ├── gms_webhook │ ├── gitlab.rb │ └── github.rb │ └── git_groupteam_member │ └── gitlab.rb ├── Rakefile ├── .travis.yml ├── Gemfile ├── metadata.json ├── CHANGELOG.md ├── LICENSE └── README.md /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Andrew Brader (@abrader) 2 | 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | 4 | -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --format s 2 | --colour 3 | --loadby mtime 4 | --backtrace 5 | -------------------------------------------------------------------------------- /github_new_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abrader/abrader-gms/HEAD/github_new_token.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | manifests/ 3 | tests/ 4 | examples/ 5 | pkg/ 6 | 7 | Gemfile.lock 8 | Gemfile.local 9 | -------------------------------------------------------------------------------- /spec/fixtures/modules/.gitignore: -------------------------------------------------------------------------------- 1 | # This keeps the modules directory available for fixtures 2 | # Ignore everything in this directory 3 | * 4 | # Except this file 5 | !.gitignore 6 | 7 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/property/read_only.rb: -------------------------------------------------------------------------------- 1 | module PuppetX 2 | module Property 3 | class ReadOnly < Puppet::Property 4 | validate do |value| 5 | fail "#{self.name.to_s} is read-only and is only available via puppet resource." 6 | end 7 | end 8 | end 9 | end 10 | 11 | -------------------------------------------------------------------------------- /lib/puppet_x/gms/provider.rb: -------------------------------------------------------------------------------- 1 | # Forward declaration(s) 2 | module PuppetX; end 3 | module PuppetX::GMS; end 4 | 5 | module PuppetX::GMS::Provider 6 | def get_token 7 | @token ||= if resource[:token] 8 | resource[:token].strip 9 | elsif resource[:token_file] 10 | File.read(resource[:token_file]).strip 11 | else 12 | raise(Puppet::Error, "github_webhook::#{calling_method}: Must provide at least one of the following attributes: token or token_file") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec-puppet' 2 | require 'webmock/rspec' 3 | require 'vcr' 4 | 5 | WebMock.disable_net_connect! 6 | 7 | VCR.configure do |c| 8 | c.cassette_library_dir = 'fixtures/vcr_cassettes' 9 | c.hook_into :webmock 10 | end 11 | 12 | if ENV['PARSER'] == 'future' 13 | RSpec.configure do |c| 14 | c.parser = 'future' 15 | end 16 | end 17 | 18 | fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures')) 19 | 20 | RSpec.configure do |c| 21 | c.module_path = File.join(fixture_path, 'modules') 22 | c.manifest_dir = File.join(fixture_path, 'manifests') 23 | end 24 | -------------------------------------------------------------------------------- /spec/unit/provider/git_webhook_github_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | provider_class = Puppet::Type.type(:git_webhook).provider(:github) 4 | 5 | describe provider_class do 6 | let(:resource) { Puppet::Type.type(:git_webhook).new( 7 | name: 'sasparilla', 8 | webhook_url: 'https://puppetmaster.example.com:8088/payload', 9 | token: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 10 | project_name: 'puppet/control', 11 | server_url: 'https://github.com', 12 | disable_ssl_verify: true, 13 | )} 14 | 15 | let(:provider) { resource.provider } 16 | 17 | it 'should be an instance of the GitHub' do 18 | expect(provider).to be_an_instance_of Puppet::Type::Git_webhook::ProviderGithub 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/rake_tasks' 2 | require 'puppet-lint/tasks/puppet-lint' 3 | require 'puppet-syntax/tasks/puppet-syntax' 4 | 5 | exclude_paths = [ 6 | "pkg/**/*", 7 | "vendor/**/*", 8 | "spec/**/*", 9 | ] 10 | 11 | PuppetLint.configuration.log_format = "%{path}:%{linenumber}:%{check}:%{KIND}:%{message}" 12 | PuppetLint.configuration.fail_on_warnings = true 13 | PuppetLint.configuration.send("disable_80chars") 14 | PuppetLint.configuration.send('disable_class_parameter_defaults') 15 | PuppetLint.configuration.send('disable_class_inherits_from_params_class') 16 | PuppetLint.configuration.ignore_paths = exclude_paths 17 | PuppetLint.configuration.relative = true 18 | PuppetSyntax.exclude_paths = exclude_paths 19 | 20 | #task :default => [:test] 21 | 22 | #desc "Run syntax, lint, and spec tests." 23 | #task :test => [ 24 | # :syntax, 25 | # :lint, 26 | # :spec, 27 | #] 28 | 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | bundler_args: --without development acceptance 4 | cache: bundler 5 | branches: 6 | only: 7 | - master 8 | before_install: rm Gemfile.lock || true 9 | sudo: false 10 | script: 11 | - "bundle exec rake lint" 12 | - "bundle exec rake syntax" 13 | - "bundle exec rake spec SPEC_OPTS='--format documentation'" 14 | rvm: 15 | - 1.9.3 16 | - 2.0.0 17 | - 2.1 18 | - 2.2 19 | env: 20 | - PUPPET_VERSION="~> 3.2.0" 21 | - PUPPET_VERSION="~> 3.3.0" 22 | - PUPPET_VERSION="~> 3.4.0" 23 | - PUPPET_VERSION="~> 3.5.1" 24 | - PUPPET_VERSION="~> 3.6.0" 25 | - PUPPET_VERSION="~> 3.7.0" 26 | - PUPPET_VERSION="~> 4.0.0" 27 | 28 | global: 29 | - PUBLISHER_LOGIN=abrader 30 | 31 | notifications: 32 | email: false 33 | webhooks: 34 | urls: 35 | - 'https://webhooks.gitter.im/e/16f24db0492933fce79d' 36 | on_success: change 37 | on_failure: always 38 | on_start: false 39 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source ENV['GEM_SOURCE'] || "https://rubygems.org" 2 | 3 | def location_for(place, fake_version = nil) 4 | if place =~ /^(git:[^#]*)#(.*)/ 5 | [fake_version, { :git => $1, :branch => $2, :require => false }].compact 6 | elsif place =~ /^file:\/\/(.*)/ 7 | ['>= 0', { :path => File.expand_path($1), :require => false }] 8 | else 9 | [place, { :require => false }] 10 | end 11 | end 12 | 13 | group :test do 14 | gem 'rake' 15 | gem 'puppet', *location_for(ENV['PUPPET_LOCATION'] || '~> 3.7.0') 16 | gem 'puppetlabs_spec_helper' 17 | gem 'webmock' 18 | gem 'vcr' 19 | gem 'rspec-puppet', :git => 'https://github.com/rodjek/rspec-puppet.git' 20 | gem 'metadata-json-lint' 21 | end 22 | 23 | group :development do 24 | gem 'travis' 25 | gem 'travis-lint' 26 | gem 'puppet-blacksmith' 27 | gem 'guard-rake' 28 | gem 'rubocop', require: false 29 | gem 'pry' 30 | gem 'librarian-puppet' 31 | end 32 | 33 | group :acceptance do 34 | gem 'mustache', '0.99.8' 35 | gem 'beaker-rspec' 36 | gem 'beaker-puppet_install_helper' 37 | end 38 | 39 | # vim:ft=ruby 40 | -------------------------------------------------------------------------------- /spec/unit/type/git_webhook_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | type_class = Puppet::Type.type(:git_webhook) 4 | 5 | describe type_class do 6 | let :params do 7 | [ 8 | :name, 9 | :webhook_url, 10 | :token, 11 | :username, 12 | :password, 13 | :project_id, 14 | :project_name, 15 | :repo_name, 16 | :hook_exe, 17 | :hook_exe_params, 18 | :merge_request_events, 19 | :tag_push_events, 20 | :issue_events, 21 | :disable_ssl_verify, 22 | :server_url, 23 | ] 24 | end 25 | 26 | let :properties do 27 | [ 28 | :ensure, 29 | ] 30 | end 31 | 32 | it 'should have expected properties' do 33 | properties.each do |prop| 34 | expect(type_class.properties.map(&:name)).to be_include(prop) 35 | end 36 | end 37 | 38 | it 'should have expected parameters' do 39 | params.each do |param| 40 | expect(type_class.parameters).to be_include(param) 41 | end 42 | end 43 | 44 | it 'should require a name' do 45 | expect { 46 | type_class.new({}) 47 | }.to raise_error(Puppet::Error, 'Title or name must be provided') 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /lib/puppet_x/gms/type.rb: -------------------------------------------------------------------------------- 1 | # Forward declaration(s) 2 | module PuppetX; end 3 | module PuppetX::GMS; end 4 | 5 | module PuppetX::GMS::Type 6 | def validate_token_or_token_file 7 | # The token and token_file parameters are mutually exclusive. It is an 8 | # error to provide both simultaneously. 9 | if !parameters[:token].nil? and !parameters[:token_file].nil? 10 | fail 'token and token_file are mutually exclusive. Only one of these parameters can be specified, not both together' 11 | end 12 | end 13 | 14 | def self.included(base) 15 | base.extend(ClassMethods) 16 | end 17 | 18 | module ClassMethods 19 | def add_parameter_token 20 | newparam(:token) do 21 | desc 'The private token require to manipulate the Git management system provider chosen.' 22 | munge do |value| 23 | String(value) 24 | end 25 | end 26 | end 27 | 28 | def add_parameter_token_file 29 | newparam(:token_file) do 30 | desc 'The path to a file on the agent containing the private token require to manipulate the Git management system provider chosen. Required. NOTE: GitHub & GitLab only.' 31 | munge do |value| 32 | String(value) 33 | end 34 | end 35 | end 36 | 37 | def add_parameter_username 38 | newparam(:username) do 39 | desc 'The username to be used to authenticate with the Stash server for API access.' 40 | munge do |value| 41 | String(value) 42 | end 43 | end 44 | end 45 | 46 | def add_parameter_password 47 | newparam(:password) do 48 | desc 'The password to be used to authenticate with the Stash server for API access.' 49 | munge do |value| 50 | String(value) 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "abrader-gms", 3 | "version": "1.0.3", 4 | "author": "Andrew Brader", 5 | "summary": "Git Management Systems API Types & Providers", 6 | "license": "Apache-2.0", 7 | "source": "https://github.com/abrader/abrader-gms", 8 | "project_page": "https://github.com/abrader/abrader-gms", 9 | "issues_url": "https://github.com/abrader/abrader-gms/issues", 10 | "tags": ["git", "github", "gitlab", "stash", "gms"], 11 | "operatingsystem_support": [ 12 | { 13 | "operatingsystem": "RedHat", 14 | "operatingsystemrelease": [ 15 | "5", 16 | "6", 17 | "7" 18 | ] 19 | }, 20 | { 21 | "operatingsystem": "CentOS", 22 | "operatingsystemrelease": [ 23 | "5", 24 | "6", 25 | "7" 26 | ] 27 | }, 28 | { 29 | "operatingsystem": "OracleLinux", 30 | "operatingsystemrelease": [ 31 | "5", 32 | "6", 33 | "7" 34 | ] 35 | }, 36 | { 37 | "operatingsystem": "Scientific", 38 | "operatingsystemrelease": [ 39 | "5", 40 | "6", 41 | "7" 42 | ] 43 | }, 44 | { 45 | "operatingsystem": "SLES", 46 | "operatingsystemrelease": [ 47 | "10 SP4", 48 | "11 SP1", 49 | "12" 50 | ] 51 | }, 52 | { 53 | "operatingsystem": "Debian", 54 | "operatingsystemrelease": [ 55 | "6", 56 | "7" 57 | ] 58 | }, 59 | { 60 | "operatingsystem": "Ubuntu", 61 | "operatingsystemrelease": [ 62 | "10.04", 63 | "12.04", 64 | "14.04" 65 | ] 66 | }, 67 | { 68 | "operatingsystem": "Solaris", 69 | "operatingsystemrelease": [ 70 | "10", 71 | "11" 72 | ] 73 | }, 74 | { 75 | "operatingsystem": "AIX", 76 | "operatingsystemrelease": [ 77 | "6.1", 78 | "7.1" 79 | ] 80 | } 81 | ], 82 | "requirements": [ 83 | { 84 | "name": "pe", 85 | "version_requirement": ">= 3.1.3 < 2015.4.0" 86 | }, 87 | { 88 | "name": "puppet", 89 | "version_requirement": ">= 3.4.3 < 5.0.0" 90 | } 91 | ], 92 | "dependencies": [ 93 | { 94 | "name": "puppetlabs/stdlib", 95 | "version_requirement": ">=3.2.0 <5.0.0" 96 | } 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2016-06-07 - Release 1.0.3 2 | ### Summary 3 | 4 | This release is an attempt to fix the permissions issue users discovered in the previous Forge package mentioned in this issue: [Wrong rights on lib/puppet/provider/gms_webhook/github.rb](https://github.com/abrader/abrader-gms/issues/28) 5 | 6 | ## 2016-02-10 - Release 1.0.2 7 | ### Summary 8 | 9 | This release is mainly a wrap of fixes and updates prior to a major rewrite 10 | 11 | #### Fixes 12 | - Deploy Keys 13 | - All Providers 14 | - Removed restriction on SSL port 15 | - Stash 16 | - Fixed issue with initial deploy key not getting created 17 | 18 | - - - 19 | 20 | ## 2015-10-20 - Release 1.0.1 21 | ### Summary 22 | 23 | This release is purely to expose debugging functionality previously assumed as released. ;) 24 | 25 | - - - 26 | 27 | ## 2015-08-24 - Release 1.0.0 28 | ### Summary 29 | 30 | This release introduces Stash support for both deploy keys and webhooks. 31 | 32 | The [README](https://github.com/abrader/abrader-gms/blob/master/README.md) has been updated to include the usage notes for these parameters. 33 | 34 | #### Features 35 | - New parameters - `Stash` 36 | - `username` 37 | - `password` 38 | - `repo_name` 39 | 40 | - - - 41 | 42 | ## 2015-06-12 - Release 0.0.9 43 | ### Summary 44 | 45 | v0.0.8 Tarball on Puppet Forge had bad permissions for the Webhook provider. Thanks to @bhechinger for the heads up. 46 | 47 | - - - 48 | 49 | ## 2015-04-20 - Release 0.0.8 50 | ### Summary 51 | 52 | This release is because I simply missed the updated CHANGELOG when packaging the previous module release. 53 | 54 | - - - 55 | 56 | ## 2015-04-20 - Release 0.0.7 57 | 58 | ### Summary 59 | 60 | This release is for the purpose of easing the regex on the git_webhook type so Basic HTTP Auth included with an URL is an accepted parameter. 61 | 62 | - - - 63 | 64 | ## 2015-02-24 - Release 0.0.6 65 | ### Summary 66 | 67 | This release has many new parameters enabling the ability to set different triggers for webhooks on GitLab and disabling ssl checks on GitHub. Minor cleanup as well. 68 | 69 | The [README](https://github.com/abrader/abrader-gms/blob/master/README.md) has been updated to include the usage notes for these parameters. 70 | 71 | #### Features 72 | - New parameters - `GitLab` 73 | - `merge_request_events` 74 | - `tag_push_events` 75 | - `issue_events` 76 | - New parameters - `GitHub` 77 | - `disable_ssl_verify` 78 | 79 | - - - 80 | -------------------------------------------------------------------------------- /lib/puppet/type/git_deploy_key.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_x/gms/type' 2 | 3 | module Puppet 4 | Puppet::Type.newtype(:git_deploy_key) do 5 | include PuppetX::GMS::Type 6 | 7 | @doc = %q{A deploy key is an SSH key that is stored on your server and grants access to a single GitHub repository. This key is attached directly to the repository instead of to a personal user account. Anyone with access to the repository and server has the ability to deploy the project. It is also beneficial for users since they are not required to change their local SSH settings. 8 | } 9 | 10 | ensurable do 11 | defaultvalues 12 | defaultto :present 13 | end 14 | 15 | newparam(:name, :namevar => true) do 16 | desc 'A unique title for the key that will be provided to the prefered Git management system.' 17 | end 18 | 19 | newparam(:path) do 20 | desc 'The file Puppet will ensure is provided to the prefered Git management system.' 21 | validate do |value| 22 | unless (Puppet.features.posix? and value =~ /^\//) or (Puppet.features.microsoft_windows? and (value =~ /^.:\// or value =~ /^\/\/[^\/]+\/[^\/]+/)) 23 | raise(Puppet::Error, "File paths must be fully qualified, not '#{value}'") 24 | end 25 | end 26 | end 27 | 28 | add_parameter_token 29 | add_parameter_token_file 30 | add_parameter_username 31 | add_parameter_password 32 | 33 | newparam(:project_id) do 34 | desc 'The project ID associated with the project.' 35 | munge do |value| 36 | Integer(value) 37 | end 38 | end 39 | 40 | newparam(:project_name) do 41 | desc 'The project name associated with the project.' 42 | munge do |value| 43 | String(value) 44 | end 45 | end 46 | 47 | newparam(:repo_name) do 48 | desc 'The repository name the deploy key will be associated. If this parameter is ommitted, the deploy key will be associated with the project instead. Optional. NOTE: Stash only.' 49 | munge do |value| 50 | String(value) 51 | end 52 | end 53 | 54 | newparam(:server_url) do 55 | desc 'The URL path to the Git management system server.' 56 | validate do |value| 57 | #unless value =~ /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/ 58 | unless value =~ /^(https?:\/\/).*:?.*\/?$/ 59 | raise(Puppet::Error, "Git server URL must be fully qualified, not '#{value}'") 60 | end 61 | end 62 | end 63 | 64 | autorequire(:file) do 65 | self[:path]if self[:path] and Pathname.new(self[:path]).absolute? 66 | end 67 | 68 | validate do 69 | validate_token_or_token_file 70 | end 71 | 72 | end 73 | end 74 | 75 | -------------------------------------------------------------------------------- /lib/puppet/type/git_groupteam.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/parameter/boolean' 2 | require 'puppet_x/gms/type' 3 | 4 | module Puppet 5 | Puppet::Type.newtype(:git_groupteam) do 6 | include PuppetX::GMS::Type 7 | 8 | @doc = %q{TODO 9 | } 10 | 11 | ensurable do 12 | defaultvalues 13 | defaultto :present 14 | end 15 | 16 | newparam(:groupteam_name, :namevar => true) do 17 | desc 'A unique title for the key that will be provided to the prefered Git management system. Required.' 18 | end 19 | 20 | add_parameter_token 21 | add_parameter_token_file 22 | 23 | # add_parameter_username 24 | # add_parameter_password 25 | 26 | # newparam(:project_id) do 27 | # desc 'The project ID associated with the project.' 28 | # munge do |value| 29 | # Integer(value) 30 | # end 31 | # end 32 | 33 | # newparam(:groupteam_name) do 34 | # desc 'The group/team name to be managed. Required.' 35 | # munge do |value| 36 | # String(value) 37 | # end 38 | # end 39 | 40 | newparam(:description) do 41 | desc 'The description associated with the group to be managed. Optional.' 42 | munge do |value| 43 | String(value) 44 | end 45 | end 46 | 47 | # newparam(:repo_name) do 48 | # desc 'The name of the repository associated with the webhook. Required. NOTE: Stash only.' 49 | # munge do |value| 50 | # String(value) 51 | # end 52 | # end 53 | 54 | # newparam(:hook_exe) do 55 | # desc 'The absolute path to the exectuable triggered when a commit has been made to the respository. Required. NOTE: Stash only.' 56 | # munge do |value| 57 | # String(value) 58 | # end 59 | # end 60 | # 61 | # newparam(:hook_exe_params) do 62 | # desc 'The parameters to be passed along side of the executable that will be triggered when a commit has been made to the repository. Optional. NOTE: Stash only.' 63 | # munge do |value| 64 | # String(value) 65 | # end 66 | # end 67 | 68 | # newparam(:merge_request_events, :boolean => true, :parent => Puppet::Parameter::Boolean) do 69 | # desc 'The URL in the webhook_url parameter will be triggered when a merge request is created. Optional. NOTE: GitLab only' 70 | # 71 | # defaultto false 72 | # end 73 | # 74 | # newparam(:tag_push_events, :boolean => true, :parent => Puppet::Parameter::Boolean) do 75 | # desc 'The URL in the webhook_url parameter will be triggered when a tag push event occurs. Optional. NOTE: GitLab only' 76 | # 77 | # defaultto false 78 | # end 79 | # 80 | # newparam(:issue_events, :boolean => true, :parent => Puppet::Parameter::Boolean) do 81 | # desc 'The URL in the webhook_url parameter will be triggered when an issue event occurs. Optional. NOTE: GitLab only.' 82 | # 83 | # defaultto false 84 | # end 85 | # 86 | # newparam(:disable_ssl_verify, :boolean => true, :parent => Puppet::Parameter::Boolean) do 87 | # desc 'Boolean value for disabling SSL verification for this webhook. Optional. NOTE: GitHub only' 88 | # 89 | # defaultto false 90 | # end 91 | 92 | newparam(:server_url) do 93 | desc 'The URL path to the Git management system server. Required.' 94 | validate do |value| 95 | #unless value =~ /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/ 96 | unless value =~ /^(https?:\/\/).*:?.*\/?$/ 97 | raise(Puppet::Error, "Git server URL must be fully qualified, not '#{value}'") 98 | end 99 | end 100 | end 101 | 102 | validate do 103 | validate_token_or_token_file 104 | end 105 | 106 | end 107 | end 108 | 109 | -------------------------------------------------------------------------------- /lib/puppet/type/git_webhook.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/parameter/boolean' 2 | require 'puppet_x/gms/type' 3 | 4 | module Puppet 5 | Puppet::Type.newtype(:git_webhook) do 6 | include PuppetX::GMS::Type 7 | 8 | @doc = %q{TODO 9 | } 10 | 11 | ensurable do 12 | defaultvalues 13 | defaultto :present 14 | end 15 | 16 | newparam(:name, :namevar => true) do 17 | desc 'A unique title for the key that will be provided to the prefered Git management system. Required.' 18 | end 19 | 20 | newparam(:webhook_url) do 21 | desc 'The URL the webhook will trigger upon a commit to the respective respository. Required. NOTE: GitHub & GitLab only.' 22 | validate do |value| 23 | unless value =~ /^(https?:\/\/)?(\S*\:\S*\@)?(\S*)\.?(\S*)\.?(\w*):?(\d*)\/?(\S*)$/ 24 | raise(Puppet::Error, "Git webhook URL must be fully qualified, not '#{value}'") 25 | end 26 | end 27 | end 28 | 29 | add_parameter_token 30 | add_parameter_token_file 31 | add_parameter_username 32 | add_parameter_password 33 | 34 | newparam(:project_id) do 35 | desc 'The project ID associated with the project.' 36 | munge do |value| 37 | Integer(value) 38 | end 39 | end 40 | 41 | newparam(:project_name) do 42 | desc 'The project name associated with the project. Required.' 43 | munge do |value| 44 | String(value) 45 | end 46 | end 47 | 48 | newparam(:repo_name) do 49 | desc 'The name of the repository associated with the webhook. Required. NOTE: Stash only.' 50 | munge do |value| 51 | String(value) 52 | end 53 | end 54 | 55 | newparam(:hook_exe) do 56 | desc 'The absolute path to the exectuable triggered when a commit has been made to the respository. Required. NOTE: Stash only.' 57 | munge do |value| 58 | String(value) 59 | end 60 | end 61 | 62 | newparam(:hook_exe_params) do 63 | desc 'The parameters to be passed along side of the executable that will be triggered when a commit has been made to the repository. Optional. NOTE: Stash only.' 64 | munge do |value| 65 | String(value) 66 | end 67 | end 68 | 69 | newparam(:merge_request_events, :boolean => true, :parent => Puppet::Parameter::Boolean) do 70 | desc 'The URL in the webhook_url parameter will be triggered when a merge request is created. Optional. NOTE: GitLab only' 71 | 72 | defaultto false 73 | end 74 | 75 | newparam(:tag_push_events, :boolean => true, :parent => Puppet::Parameter::Boolean) do 76 | desc 'The URL in the webhook_url parameter will be triggered when a tag push event occurs. Optional. NOTE: GitLab only' 77 | 78 | defaultto false 79 | end 80 | 81 | newparam(:issue_events, :boolean => true, :parent => Puppet::Parameter::Boolean) do 82 | desc 'The URL in the webhook_url parameter will be triggered when an issue event occurs. Optional. NOTE: GitLab only.' 83 | 84 | defaultto false 85 | end 86 | 87 | newparam(:disable_ssl_verify, :boolean => true, :parent => Puppet::Parameter::Boolean) do 88 | desc 'Boolean value for disabling SSL verification for this webhook. Optional. NOTE: GitHub only' 89 | 90 | defaultto false 91 | end 92 | 93 | newparam(:server_url) do 94 | desc 'The URL path to the Git management system server. Required.' 95 | validate do |value| 96 | #unless value =~ /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/ 97 | unless value =~ /^(https?:\/\/).*:?.*\/?$/ 98 | raise(Puppet::Error, "Git server URL must be fully qualified, not '#{value}'") 99 | end 100 | end 101 | end 102 | 103 | validate do 104 | validate_token_or_token_file 105 | end 106 | 107 | end 108 | end 109 | 110 | -------------------------------------------------------------------------------- /lib/puppet/type/git_groupteam_member.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/parameter/boolean' 2 | require 'puppet_x/gms/type' 3 | 4 | module Puppet 5 | Puppet::Type.newtype(:git_groupteam_member) do 6 | include PuppetX::GMS::Type 7 | 8 | @doc = %q{TODO 9 | } 10 | 11 | ensurable do 12 | defaultvalues 13 | defaultto :present 14 | end 15 | 16 | newparam(:groupteam_name, :namevar => true) do 17 | desc 'A unique title for the key that will be provided to the prefered Git management system. Required.' 18 | end 19 | 20 | add_parameter_token 21 | add_parameter_token_file 22 | 23 | # add_parameter_username 24 | # add_parameter_password 25 | 26 | # newparam(:project_id) do 27 | # desc 'The project ID associated with the project.' 28 | # munge do |value| 29 | # Integer(value) 30 | # end 31 | # end 32 | 33 | # newparam(:groupteam_name) do 34 | # desc 'The group/team name to be managed. Required.' 35 | # munge do |value| 36 | # String(value) 37 | # end 38 | # end 39 | 40 | newparam(:member_name) do 41 | desc 'The member name to be managed in regards to the group/team. Required.' 42 | munge do |value| 43 | String(value) 44 | end 45 | end 46 | 47 | newparam(:access_level) do 48 | desc 'The access level associated with member being managed in regards to group/team. Required.' 49 | munge do |value| 50 | String(value) 51 | end 52 | end 53 | 54 | # newparam(:repo_name) do 55 | # desc 'The name of the repository associated with the webhook. Required. NOTE: Stash only.' 56 | # munge do |value| 57 | # String(value) 58 | # end 59 | # end 60 | 61 | # newparam(:hook_exe) do 62 | # desc 'The absolute path to the exectuable triggered when a commit has been made to the respository. Required. NOTE: Stash only.' 63 | # munge do |value| 64 | # String(value) 65 | # end 66 | # end 67 | # 68 | # newparam(:hook_exe_params) do 69 | # desc 'The parameters to be passed along side of the executable that will be triggered when a commit has been made to the repository. Optional. NOTE: Stash only.' 70 | # munge do |value| 71 | # String(value) 72 | # end 73 | # end 74 | 75 | # newparam(:merge_request_events, :boolean => true, :parent => Puppet::Parameter::Boolean) do 76 | # desc 'The URL in the webhook_url parameter will be triggered when a merge request is created. Optional. NOTE: GitLab only' 77 | # 78 | # defaultto false 79 | # end 80 | # 81 | # newparam(:tag_push_events, :boolean => true, :parent => Puppet::Parameter::Boolean) do 82 | # desc 'The URL in the webhook_url parameter will be triggered when a tag push event occurs. Optional. NOTE: GitLab only' 83 | # 84 | # defaultto false 85 | # end 86 | # 87 | # newparam(:issue_events, :boolean => true, :parent => Puppet::Parameter::Boolean) do 88 | # desc 'The URL in the webhook_url parameter will be triggered when an issue event occurs. Optional. NOTE: GitLab only.' 89 | # 90 | # defaultto false 91 | # end 92 | # 93 | # newparam(:disable_ssl_verify, :boolean => true, :parent => Puppet::Parameter::Boolean) do 94 | # desc 'Boolean value for disabling SSL verification for this webhook. Optional. NOTE: GitHub only' 95 | # 96 | # defaultto false 97 | # end 98 | 99 | newparam(:server_url) do 100 | desc 'The URL path to the Git management system server. Required.' 101 | validate do |value| 102 | #unless value =~ /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/ 103 | unless value =~ /^(https?:\/\/).*:?.*\/?$/ 104 | raise(Puppet::Error, "Git server URL must be fully qualified, not '#{value}'") 105 | end 106 | end 107 | end 108 | 109 | validate do 110 | validate_token_or_token_file 111 | end 112 | 113 | end 114 | end 115 | 116 | -------------------------------------------------------------------------------- /lib/puppet/provider/git_deploy_key/github.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'net/http' 3 | require 'json' 4 | require 'puppet_x/gms/provider' 5 | 6 | Puppet::Type.type(:git_deploy_key).provide(:github) do 7 | include PuppetX::GMS::Provider 8 | 9 | defaultfor :github => :exist 10 | defaultfor :feature => :posix 11 | 12 | def gms_server 13 | return resource[:server_url].strip unless resource[:server_url].nil? 14 | return 'https://api.github.com' 15 | end 16 | 17 | def calling_method 18 | # Get calling method and clean it up for good reporting 19 | cm = String.new 20 | cm = caller[0].split(" ").last 21 | cm.tr!('\'', '') 22 | cm.tr!('\`','') 23 | cm 24 | end 25 | 26 | def api_call(action,url,data = nil) 27 | uri = URI.parse(url) 28 | 29 | http = Net::HTTP.new(uri.host, uri.port) 30 | 31 | if uri.port == 443 or uri.scheme == 'https' 32 | http.use_ssl = true 33 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 34 | else 35 | http.use_ssl = false 36 | end 37 | 38 | if Puppet[:debug] == true 39 | http.set_debug_output($stdout) 40 | end 41 | 42 | if action =~ /post/i 43 | req = Net::HTTP::Post.new(uri.request_uri) 44 | elsif action =~ /put/i 45 | req = Net::HTTP::Put.new(uri.request_uri) 46 | elsif action =~ /delete/i 47 | req = Net::HTTP::Delete.new(uri.request_uri) 48 | else 49 | req = Net::HTTP::Get.new(uri.request_uri) 50 | end 51 | 52 | req.initialize_http_header({'Accept' => 'application/vnd.github.v3+json', 'User-Agent' => 'puppet-gms'}) 53 | req.set_content_type('application/json') 54 | req.add_field('Authorization', "token #{get_token}") 55 | #req.add_field('PRIVATE-TOKEN', get_token) 56 | 57 | if data 58 | req.body = data.to_json 59 | end 60 | 61 | Puppet.debug("github_deploy_key::#{calling_method}: REST API #{req.method} Endpoint: #{uri.to_s}") 62 | Puppet.debug("github_deploy_key::#{calling_method}: REST API #{req.method} Request: #{req.inspect}") 63 | 64 | response = http.request(req) 65 | 66 | Puppet.debug("github_deploy_key::#{calling_method}: REST API #{req.method} Response: #{response.inspect}") 67 | 68 | response 69 | end 70 | 71 | def exists? 72 | key_hash = Hash.new 73 | url = "#{gms_server}/repos/#{resource[:project_name].strip}/keys" 74 | 75 | response = api_call('GET', url) 76 | 77 | key_json = JSON.parse(response.body) 78 | 79 | key_json.each do |child| 80 | if child['key'].split(" ")[1].eql?(File.read(resource[:path].strip).split(" ")[1]) 81 | return true 82 | end 83 | end 84 | 85 | return false 86 | end 87 | 88 | def get_key_id 89 | key_hash = Hash.new 90 | url = "#{gms_server}/repos/#{resource[:project_name].strip}/keys" 91 | 92 | response = api_call('GET', url) 93 | 94 | key_json = JSON.parse(response.body) 95 | 96 | key_json.each do |child| 97 | if child['key'].split(" ")[1].eql?(File.read(resource[:path].strip).split(" ")[1]) 98 | return child['id'].to_s 99 | end 100 | end 101 | 102 | raise(Puppet::Error, "github_deploy_key::#{calling_method}: Unable to find nonexistent project name \'#{resource[:project_name].strip}\' to retrieve corresponding ID") 103 | return nil 104 | end 105 | 106 | def create 107 | url = "#{gms_server}/repos/#{resource[:project_name].strip}/keys" 108 | 109 | begin 110 | response = api_call('POST', url, {'title' => resource[:name].strip, 'key' => File.read(resource[:path].strip)}) 111 | 112 | if (response.class == Net::HTTPCreated) 113 | return true 114 | else 115 | raise(Puppet::Error, "github_deploy_key::#{calling_method}: #{response.inspect}") 116 | end 117 | rescue Exception => e 118 | raise(Puppet::Error, "github_deploy_key::#{calling_method}: #{e.message}") 119 | end 120 | end 121 | 122 | def destroy 123 | key_id = get_key_id 124 | 125 | unless key_id.nil? 126 | url = "#{gms_server}/repos/#{resource[:project_name].strip}/keys/#{key_id}" 127 | 128 | begin 129 | response = api_call('DELETE', url) 130 | 131 | if response.class == Net::HTTPNoContent 132 | return true 133 | else 134 | raise(Puppet::Error, "github_deploy_key::#{calling_method}: #{response.inspect}") 135 | end 136 | rescue Exception => e 137 | raise(Puppet::Error, "github_deploy_key::#{calling_method}: #{e.message}") 138 | end 139 | 140 | end 141 | end 142 | 143 | end 144 | 145 | 146 | -------------------------------------------------------------------------------- /lib/puppet/provider/git_groupteam/gitlab.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'net/http' 3 | require 'json' 4 | require 'puppet_x/gms/provider' 5 | 6 | Puppet::Type.type(:git_groupteam).provide(:gitlab) do 7 | include PuppetX::GMS::Provider 8 | 9 | defaultfor :gitlab => :exists 10 | 11 | def gms_server 12 | return resource[:server_url].strip unless resource[:server_url].nil? 13 | return 'https://gitlab.com' 14 | end 15 | 16 | def calling_method 17 | # Get calling method and clean it up for good reporting 18 | cm = String.new 19 | cm = caller[0].split(" ").last 20 | cm.tr!('\'', '') 21 | cm.tr!('\`','') 22 | cm 23 | end 24 | 25 | def api_call(action,url,data = nil) 26 | uri = URI.parse(url) 27 | 28 | http = Net::HTTP.new(uri.host, uri.port) 29 | 30 | if uri.port == 443 or uri.scheme == 'https' 31 | http.use_ssl = true 32 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 33 | else 34 | http.use_ssl = false 35 | end 36 | 37 | if Puppet[:debug] == true 38 | http.set_debug_output($stdout) 39 | end 40 | 41 | if action =~ /post/i 42 | req = Net::HTTP::Post.new(uri.request_uri) 43 | elsif action =~ /put/i 44 | req = Net::HTTP::Put.new(uri.request_uri) 45 | elsif action =~ /delete/i 46 | req = Net::HTTP::Delete.new(uri.request_uri) 47 | else 48 | req = Net::HTTP::Get.new(uri.request_uri) 49 | end 50 | 51 | req.set_content_type('application/json') 52 | req.add_field('PRIVATE-TOKEN', get_token) 53 | 54 | if data 55 | req.body = data.to_json 56 | end 57 | 58 | Puppet.debug("gitlab_groupteam::#{calling_method}: REST API #{req.method} Endpoint: #{uri.to_s}") 59 | Puppet.debug("gitlab_groupteam::#{calling_method}: REST API #{req.method} Request: #{req.inspect}") 60 | 61 | response = http.request(req) 62 | 63 | Puppet.debug("gitlab_groupteam::#{calling_method}: REST API #{req.method} Response: #{response.inspect}") 64 | 65 | response 66 | end 67 | 68 | def exists? 69 | groupteam_hash = Hash.new 70 | url = "#{gms_server}/api/v3/groups" 71 | 72 | response = api_call('GET', url) 73 | 74 | groupteam_json = JSON.parse(response.body) 75 | 76 | groupteam_json.each do |child| 77 | groupteam_hash[child['name']] = child['description'] 78 | end 79 | 80 | groupteam_hash.each do |k,v| 81 | if k.eql?(resource[:groupteam_name].strip) 82 | unless (v.nil? && resource[:description].nil?) || (v && resource[:description]) && v.eql?(resource[:description].strip) 83 | destroy() 84 | Puppet.debug "gitlab_groupteam::#{calling_method}: Group \'#{resource[:name]}\' exists but not as resource block indicates. Recreating..." 85 | return false 86 | end 87 | Puppet.debug "gitlab_groupteam::#{calling_method}: Group \'#{resource[:name]}\' already exists" 88 | return true 89 | end 90 | end 91 | 92 | Puppet.debug "gitlab_groupteam::#{calling_method}: Group \'#{resource[:name]}\' does not currently exist" 93 | return false 94 | end 95 | 96 | def get_group_id 97 | group_hash = Hash.new 98 | 99 | url = "#{gms_server}/api/v3/groups" 100 | 101 | response = api_call('GET', url) 102 | 103 | group_json = JSON.parse(response.body) 104 | 105 | group_json.each do |child| 106 | group_hash[child['name']] = child['id'] 107 | end 108 | 109 | group_hash.each do |k,v| 110 | if k.eql?(resource[:groupteam_name].strip) 111 | return v.to_i 112 | end 113 | end 114 | 115 | raise(Puppet::Error, "gitlab_groupteam_member::#{calling_method}: Unable to find nonexistent group \'#{resource[:groupteam_name].strip}\'") 116 | return nil 117 | end 118 | 119 | def create 120 | url = "#{gms_server}/api/v3/groups" 121 | 122 | begin 123 | opts = { 'name' => resource[:groupteam_name].strip, 'path' => resource[:groupteam_name].strip } 124 | 125 | unless resource[:description].nil? 126 | opts['description'] = resource[:description].strip 127 | end 128 | 129 | Puppet.debug("opts => #{opts.inspect}") 130 | 131 | response = api_call('POST', url, opts) 132 | 133 | if (response.class == Net::HTTPCreated) 134 | return true 135 | else 136 | raise(Puppet::Error, "gitlab_groupteam::#{calling_method}: #{response.inspect}") 137 | end 138 | rescue Exception => e 139 | raise(Puppet::Error, "gitlab_groupteam::#{calling_method}: #{e.message}") 140 | end 141 | end 142 | 143 | def destroy 144 | group_id = get_group_id 145 | 146 | unless group_id.nil? 147 | url = "#{gms_server}/api/v3/groups/#{group_id}" 148 | 149 | begin 150 | response = api_call('DELETE', url) 151 | 152 | if response.class == Net::HTTPOK 153 | return true 154 | else 155 | raise(Puppet::Error, "gitlab_groupteam::#{calling_method}: #{response.inspect}") 156 | end 157 | rescue Exception => e 158 | raise(Puppet::Error, "gitlab_groupteam::#{calling_method}: #{e.message}") 159 | end 160 | 161 | end 162 | end 163 | 164 | end 165 | 166 | 167 | -------------------------------------------------------------------------------- /lib/puppet/provider/git_deploy_key/gitlab.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'net/http' 3 | require 'json' 4 | require 'puppet_x/gms/provider' 5 | 6 | Puppet::Type.type(:git_deploy_key).provide(:gitlab) do 7 | include PuppetX::GMS::Provider 8 | 9 | defaultfor :gitlab => :exists 10 | 11 | def gms_server 12 | return resource[:server_url].strip unless resource[:server_url].nil? 13 | return 'https://gitlab.com' 14 | end 15 | 16 | def calling_method 17 | # Get calling method and clean it up for good reporting 18 | cm = String.new 19 | cm = caller[0].split(" ").last 20 | cm.tr!('\'', '') 21 | cm.tr!('\`','') 22 | cm 23 | end 24 | 25 | def api_call(action,url,data = nil) 26 | uri = URI.parse(url) 27 | 28 | http = Net::HTTP.new(uri.host, uri.port) 29 | 30 | if uri.port == 443 or uri.scheme == 'https' 31 | http.use_ssl = true 32 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 33 | else 34 | http.use_ssl = false 35 | end 36 | 37 | if Puppet[:debug] == true 38 | http.set_debug_output($stdout) 39 | end 40 | 41 | if action =~ /post/i 42 | req = Net::HTTP::Post.new(uri.request_uri) 43 | elsif action =~ /put/i 44 | req = Net::HTTP::Put.new(uri.request_uri) 45 | elsif action =~ /delete/i 46 | req = Net::HTTP::Delete.new(uri.request_uri) 47 | else 48 | req = Net::HTTP::Get.new(uri.request_uri) 49 | end 50 | 51 | req.set_content_type('application/json') 52 | req.add_field('PRIVATE-TOKEN', get_token) 53 | 54 | if data 55 | req.body = data.to_json 56 | end 57 | 58 | Puppet.debug("gitlab_deploy_key::#{calling_method}: REST API #{req.method} Endpoint: #{uri.to_s}") 59 | Puppet.debug("gitlab_deploy_key::#{calling_method}: REST API #{req.method} Request: #{req.inspect}") 60 | 61 | response = http.request(req) 62 | 63 | Puppet.debug("gitlab_deploy_key::#{calling_method}: REST API #{req.method} Response: #{response.inspect}") 64 | 65 | response 66 | end 67 | 68 | def exists? 69 | project_id = get_project_id 70 | 71 | sshkey_hash = Hash.new 72 | url = "#{gms_server}/api/v3/projects/#{project_id}/keys" 73 | 74 | response = api_call('GET', url) 75 | 76 | sshkey_json = JSON.parse(response.body) 77 | sshkey_json.each do |child| 78 | sshkey_hash[child['key']] = child['id'] 79 | end 80 | 81 | sshkey_hash.keys.each do |k| 82 | if k.eql?(File.read(resource[:path]).strip) 83 | Puppet.debug "gitlab_deploy_key::#{calling_method}: Deploy key already exists as specified in calling resource block." 84 | return true 85 | end 86 | end 87 | 88 | Puppet.debug "gitlab_deploy_key::#{calling_method}: Deploy key does not currently exist as specified in calling resource block." 89 | return false 90 | end 91 | 92 | def get_project_id 93 | return resource[:project_id].to_i unless resource[:project_id].nil? 94 | 95 | if resource[:project_name].nil? 96 | raise(Puppet::Error, "gitlab_deploy_key::#{calling_method}: Must provide at least one of the following attributes: project_id or project_name") 97 | end 98 | 99 | project_name = resource[:project_name].strip.sub('/','%2F') 100 | 101 | url = "#{gms_server}/api/v3/projects/#{project_name}" 102 | 103 | begin 104 | response = api_call('GET', url) 105 | return JSON.parse(response.body)['id'].to_i 106 | rescue Exception => e 107 | fail(Puppet::Error, "gitlab_deploy_key::#{calling_method}: #{e.message}") 108 | return nil 109 | end 110 | 111 | end 112 | 113 | def get_key_id 114 | project_id = get_project_id 115 | 116 | keys_hash = Hash.new 117 | 118 | url = "#{gms_server}/api/v3/projects/#{project_id}/keys" 119 | 120 | response = api_call('GET', url) 121 | 122 | keys_json = JSON.parse(response.body) 123 | 124 | keys_json.each do |child| 125 | keys_hash[child['key']] = child['id'] 126 | end 127 | 128 | keys_hash.each do |k,v| 129 | if k.eql?(File.read(resource[:path]).strip) 130 | return v.to_i 131 | end 132 | end 133 | 134 | raise(Puppet::Error, "gitlab_deploy_key::#{calling_method}: Unable to find nonexistent project ID \'#{resource[:project_name].strip}\' to retrieve corresponding key ID") 135 | return nil 136 | end 137 | 138 | def create 139 | project_id = get_project_id 140 | 141 | url = "#{gms_server}/api/v3/projects/#{project_id}/keys" 142 | 143 | begin 144 | response = api_call('POST', url, {'title' => resource[:name].strip, 'key' => File.read(resource[:path].strip)}) 145 | 146 | if (response.class == Net::HTTPCreated) 147 | return true 148 | else 149 | raise(Puppet::Error, "gitlab_deploy_key::#{calling_method}: #{response.inspect}") 150 | end 151 | rescue Exception => e 152 | raise(Puppet::Error, "gitlab_deploy_key::#{calling_method}: #{e.message}") 153 | end 154 | end 155 | 156 | def destroy 157 | project_id = get_project_id 158 | 159 | key_id = get_key_id 160 | 161 | unless key_id.nil? 162 | url = "#{gms_server}/api/v3/projects/#{project_id}/keys/#{key_id}" 163 | 164 | begin 165 | response = api_call('DELETE', url) 166 | 167 | if response.class == Net::HTTPOK 168 | return true 169 | else 170 | raise(Puppet::Error, "gitlab_deploy_key::#{calling_method}: #{response.inspect}") 171 | end 172 | rescue Exception => e 173 | raise(Puppet::Error, "gitlab_deploy_key::#{calling_method}: #{e.message}") 174 | end 175 | 176 | end 177 | end 178 | 179 | end 180 | 181 | 182 | -------------------------------------------------------------------------------- /lib/puppet/provider/git_webhook/github.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'net/http' 3 | require 'json' 4 | require 'puppet_x/gms/provider' 5 | 6 | Puppet::Type.type(:git_webhook).provide(:github) do 7 | include PuppetX::GMS::Provider 8 | 9 | defaultfor :github => :exist 10 | defaultfor :feature => :posix 11 | 12 | def gms_server 13 | # Provide the host and port portion of the URL to calling methods 14 | return resource[:server_url].strip unless resource[:server_url].nil? 15 | return 'https://api.github.com' 16 | end 17 | 18 | def calling_method 19 | # Get calling method and clean it up for good reporting 20 | cm = String.new 21 | cm = caller[0].split(" ").last 22 | cm.tr!('\'', '') 23 | cm.tr!('\`','') 24 | cm 25 | end 26 | 27 | def api_call(action,url,data = nil) 28 | # Single method to make all calls to the respective RESTful API 29 | uri = URI.parse(url) 30 | 31 | http = Net::HTTP.new(uri.host, uri.port) 32 | 33 | if uri.port == 443 or uri.scheme == 'https' 34 | http.use_ssl = true 35 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 36 | else 37 | http.use_ssl = false 38 | end 39 | 40 | if Puppet[:debug] == true 41 | http.set_debug_output($stdout) 42 | end 43 | 44 | if action =~ /post/i 45 | req = Net::HTTP::Post.new(uri.request_uri) 46 | elsif action =~ /put/i 47 | req = Net::HTTP::Put.new(uri.request_uri) 48 | elsif action =~ /delete/i 49 | req = Net::HTTP::Delete.new(uri.request_uri) 50 | else 51 | req = Net::HTTP::Get.new(uri.request_uri) 52 | end 53 | 54 | req.initialize_http_header({'Accept' => 'application/vnd.github.v3+json', 'User-Agent' => 'puppet-gms'}) 55 | req.set_content_type('application/json') 56 | req.add_field('Authorization', "token #{get_token}") 57 | 58 | if data 59 | req.body = data.to_json 60 | end 61 | 62 | Puppet.debug("github_webhook::#{calling_method}: REST API #{req.method} Endpoint: #{uri.to_s}") 63 | Puppet.debug("github_webhook::#{calling_method}: REST API #{req.method} Request: #{req.inspect}") 64 | 65 | response = http.request(req) 66 | 67 | Puppet.debug("github_webhook::#{calling_method}: REST API #{req.method} Response: #{response.inspect}") 68 | 69 | response 70 | end 71 | 72 | def exists? 73 | webhook_hash = Hash.new 74 | url = "#{gms_server}/repos/#{resource[:project_name].strip}/hooks" 75 | 76 | response = api_call('GET', url) 77 | webhook_json = JSON.parse(response.body) 78 | 79 | webhook_json.each do |child| 80 | webhook_hash[child['config']['url']] = child['id'] 81 | end 82 | 83 | webhook_hash.keys.each do |k| 84 | if k.eql?(resource[:webhook_url].strip) 85 | Puppet.debug "github_webhook::#{calling_method}: Webhook already exists as specified in calling resource block." 86 | return true 87 | end 88 | end 89 | 90 | Puppet.debug "github_webhook::#{calling_method}: Webhook does not currently exist as specified in calling resource block." 91 | return false 92 | end 93 | 94 | def get_project_id 95 | return resource[:project_id].to_i unless resource[:project_id].nil? 96 | 97 | if resource[:project_name].nil? 98 | raise(Puppet::Error, "github_webhook::#{calling_method}: Must provide at least one of the following attributes: project_id or project_name") 99 | end 100 | 101 | project_name = resource[:project_name].strip 102 | 103 | url = "#{gms_server}/repos/#{project_name}" 104 | 105 | begin 106 | response = api_call('GET', url) 107 | return JSON.parse(response.body)['id'].to_i 108 | rescue Exception => e 109 | fail(Puppet::Error, "github_webhook::#{calling_method}: #{e.backtrace}") 110 | return nil 111 | end 112 | 113 | end 114 | 115 | def get_webhook_id 116 | webhook_hash = Hash.new 117 | 118 | url = "#{gms_server}/repos/#{resource[:project_name]}/hooks" 119 | 120 | response = api_call('GET', url) 121 | 122 | webhook_json = JSON.parse(response.body) 123 | 124 | webhook_json.each do |child| 125 | webhook_hash[child['config']['url']] = child['id'] 126 | end 127 | 128 | webhook_hash.each do |k,v| 129 | if k.eql?(resource[:webhook_url].strip) 130 | return v.to_i 131 | end 132 | end 133 | 134 | return nil 135 | end 136 | 137 | def create 138 | url = "#{gms_server}/repos/#{resource[:project_name].strip}/hooks" 139 | 140 | begin 141 | config_opts = { 'url' => resource[:webhook_url].strip, 'content_type' => 'json' } 142 | 143 | if resource.disable_ssl_verify? 144 | if resource[:disable_ssl_verify] == true 145 | config_opts['insecure_ssl'] = 1 146 | else 147 | config_opts['insecure_ssl'] = 0 148 | end 149 | end 150 | 151 | response = api_call('POST', url, { 'name' => 'web', 'active' => true, 'config' => config_opts }) 152 | 153 | if response.class == Net::HTTPCreated 154 | return true 155 | else 156 | raise(Puppet::Error, "github_webhook::#{calling_method}: #{response.inspect}") 157 | end 158 | rescue Exception => e 159 | raise(Puppet::Error, "github_webhook::#{calling_method}: #{e.message}") 160 | end 161 | end 162 | 163 | def destroy 164 | webhook_id = get_webhook_id 165 | 166 | unless webhook_id.nil? 167 | url = "#{gms_server}/repos/#{resource[:project_name].strip}/hooks/#{webhook_id}" 168 | 169 | begin 170 | response = api_call('DELETE', url) 171 | 172 | if (response.class == Net::HTTPNoContent) 173 | return true 174 | else 175 | raise(Puppet::Error, "github_webhook::#{calling_method}: #{response.inspect}") 176 | end 177 | rescue Exception => e 178 | raise(Puppet::Error, "github_webhook::#{calling_method}: #{e.message}") 179 | end 180 | 181 | end 182 | end 183 | 184 | end 185 | 186 | 187 | -------------------------------------------------------------------------------- /lib/puppet/provider/git_webhook/gitlab.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'net/http' 3 | require 'json' 4 | require 'puppet_x/gms/provider' 5 | 6 | Puppet::Type.type(:git_webhook).provide(:gitlab) do 7 | include PuppetX::GMS::Provider 8 | 9 | defaultfor :gitlab => :exists 10 | 11 | def gms_server 12 | return resource[:server_url].strip unless resource[:server_url].nil? 13 | return 'https://gitlab.com' 14 | end 15 | 16 | def calling_method 17 | # Get calling method and clean it up for good reporting 18 | cm = String.new 19 | cm = caller[0].split(" ").last 20 | cm.tr!('\'', '') 21 | cm.tr!('\`','') 22 | cm 23 | end 24 | 25 | def api_call(action,url,data = nil) 26 | uri = URI.parse(url) 27 | 28 | http = Net::HTTP.new(uri.host, uri.port) 29 | 30 | if uri.port == 443 or uri.scheme == 'https' 31 | http.use_ssl = true 32 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 33 | else 34 | http.use_ssl = false 35 | end 36 | 37 | if Puppet[:debug] == true 38 | http.set_debug_output($stdout) 39 | end 40 | 41 | if action =~ /post/i 42 | req = Net::HTTP::Post.new(uri.request_uri) 43 | elsif action =~ /put/i 44 | req = Net::HTTP::Put.new(uri.request_uri) 45 | elsif action =~ /delete/i 46 | req = Net::HTTP::Delete.new(uri.request_uri) 47 | else 48 | req = Net::HTTP::Get.new(uri.request_uri) 49 | end 50 | 51 | req.set_content_type('application/json') 52 | req.add_field('PRIVATE-TOKEN', get_token) 53 | 54 | if data 55 | req.body = data.to_json 56 | end 57 | 58 | Puppet.debug("gitlab_webhook::#{calling_method}: REST API #{req.method} Endpoint: #{uri.to_s}") 59 | Puppet.debug("gitlab_webhook::#{calling_method}: REST API #{req.method} Request: #{req.inspect}") 60 | 61 | response = http.request(req) 62 | 63 | Puppet.debug("gitlab_webhook::#{calling_method}: REST API #{req.method} Response: #{response.inspect}") 64 | 65 | response 66 | end 67 | 68 | def exists? 69 | project_id = get_project_id 70 | 71 | webhook_hash = Hash.new 72 | url = "#{gms_server}/api/v3/projects/#{project_id}/hooks" 73 | 74 | response = api_call('GET', url) 75 | 76 | webhook_json = JSON.parse(response.body) 77 | 78 | webhook_json.each do |child| 79 | webhook_hash[child['url']] = child['id'] 80 | end 81 | 82 | webhook_hash.keys.each do |k| 83 | if k.eql?(resource[:webhook_url].strip) 84 | Puppet.debug "gitlab_webhook::#{calling_method}: Webhook already exists as specified in calling resource block." 85 | return true 86 | end 87 | end 88 | 89 | Puppet.debug "gitlab_webhook::#{calling_method}: Webhook does not currently exist as specified in calling resource block." 90 | return false 91 | end 92 | 93 | def get_project_id 94 | return resource[:project_id].to_i unless resource[:project_id].nil? 95 | 96 | if resource[:project_name].nil? 97 | raise(Puppet::Error, "gitlab_webhook::#{calling_method}: Must provide at least one of the following attributes: project_id or project_name") 98 | end 99 | 100 | project_name = resource[:project_name].strip.sub('/','%2F') 101 | 102 | url = "#{gms_server}/api/v3/projects/#{project_name}" 103 | 104 | begin 105 | response = api_call('GET', url) 106 | return JSON.parse(response.body)['id'].to_i 107 | rescue Exception => e 108 | fail(Puppet::Error, "gitlab_webhook::#{calling_method}: #{e.message}") 109 | return nil 110 | end 111 | 112 | end 113 | 114 | def get_webhook_id 115 | project_id = get_project_id 116 | 117 | webhook_hash = Hash.new 118 | 119 | url = "#{gms_server}/api/v3/projects/#{project_id}/hooks" 120 | 121 | response = api_call('GET', url) 122 | 123 | webhook_json = JSON.parse(response.body) 124 | 125 | webhook_json.each do |child| 126 | webhook_hash[child['url']] = child['id'] 127 | end 128 | 129 | webhook_hash.each do |k,v| 130 | if k.eql?(resource[:webhook_url].strip) 131 | return v.to_i 132 | end 133 | end 134 | 135 | return nil 136 | end 137 | 138 | def create 139 | project_id = get_project_id 140 | 141 | url = "#{gms_server}/api/v3/projects/#{project_id}/hooks" 142 | 143 | begin 144 | opts = { 'url' => resource[:webhook_url].strip } 145 | 146 | if resource.disable_ssl_verify? 147 | if resource[:disable_ssl_verify] == true 148 | opts['enable_ssl_verification'] = 'false' 149 | else 150 | opts['enable_ssl_verification'] = 'true' 151 | end 152 | end 153 | 154 | if resource.merge_request_events? 155 | opts['merge_requests_events'] = resource[:merge_request_events] 156 | end 157 | 158 | if resource.tag_push_events? 159 | opts['tag_push_events'] = resource[:tag_push_events] 160 | end 161 | 162 | if resource.issue_events? 163 | opts['issues_events'] = resource[:issue_events] 164 | end 165 | 166 | response = api_call('POST', url, opts) 167 | 168 | if (response.class == Net::HTTPCreated) 169 | return true 170 | else 171 | raise(Puppet::Error, "gitlab_webhook::#{calling_method}: #{response.inspect}") 172 | end 173 | rescue Exception => e 174 | raise(Puppet::Error, "gitlab_webhook::#{calling_method}: #{e.message}") 175 | end 176 | end 177 | 178 | def destroy 179 | project_id = get_project_id 180 | 181 | webhook_id = get_webhook_id 182 | 183 | unless webhook_id.nil? 184 | url = "#{gms_server}/api/v3/projects/#{project_id}/hooks/#{webhook_id}" 185 | 186 | begin 187 | response = api_call('DELETE', url) 188 | 189 | if (response.class == Net::HTTPOK) 190 | return true 191 | else 192 | raise(Puppet::Error, "gitlab_webhook::#{calling_method}: #{response.inspect}") 193 | end 194 | rescue Exception => e 195 | raise(Puppet::Error, "gitlab_webhook::#{calling_method}: #{e.message}") 196 | end 197 | 198 | end 199 | end 200 | 201 | end 202 | 203 | 204 | -------------------------------------------------------------------------------- /lib/puppet/type/gms_webhook.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../puppet_x/puppetlabs/property/read_only' 2 | require 'puppet_x/gms/type' 3 | 4 | Puppet::Type.newtype(:gms_webhook) do 5 | include PuppetX::GMS::Type 6 | 7 | @doc = 'To manage webhooks on major GMS systems.' 8 | 9 | ensurable 10 | 11 | newparam(:name) 12 | 13 | newproperty(:active) do 14 | desc 'Boolean to make webhook active or inactive. GitHub only.' 15 | 16 | newvalues(true, false) 17 | end 18 | 19 | newproperty(:webhook_url) do 20 | desc 'The URL the webhook will trigger upon a commit to the respective respository. Required. NOTE: GitHub & GitLab only.' 21 | 22 | munge do |value| 23 | value.to_s 24 | end 25 | 26 | validate do |value| 27 | unless value =~ /^(https?:\/\/)?(\S*\:\S*\@)?(\S*)\.?(\S*)\.?(\w*):?(\d*)\/?(\S*)$/ 28 | raise(Puppet::Error, "Git webhook URL must be fully qualified, not '#{value}'") 29 | end 30 | end 31 | end 32 | 33 | newproperty(:content_type) do 34 | desc 'TODO' 35 | 36 | munge do |value| 37 | value.to_s 38 | end 39 | end 40 | 41 | newproperty(:events, :array_matching => :all) do 42 | desc 'Events that should trigger the activation of the webhook' 43 | 44 | newvalues("commit_comment", "create", "delete", "deployment", "deployment_status", "fork", "gollum", "issue_comment", "issues", "member", "public", "pull_request", "pull_request_review_comment", "push", "release", "status", "team_add", "watch") 45 | 46 | def insync?(is) 47 | is.to_set == should.to_set 48 | end 49 | end 50 | 51 | add_parameter_token 52 | add_parameter_token_file 53 | add_parameter_username 54 | add_parameter_password 55 | 56 | newproperty(:project_name) do 57 | desc 'The project name associated with the project. Required.' 58 | 59 | munge do |value| 60 | value.to_s 61 | end 62 | end 63 | 64 | newproperty(:repo_name) do 65 | desc 'The name of the repository associated with the webhook. Required. NOTE: Stash only.' 66 | end 67 | 68 | newproperty(:hook_exe) do 69 | desc 'The absolute path to the exectuable triggered when a commit has been made to the respository. Required. NOTE: Stash only.' 70 | end 71 | 72 | newproperty(:hook_exe_params) do 73 | desc 'The parameters to be passed along side of the executable that will be triggered when a commit has been made to the repository. Optional. NOTE: Stash only.' 74 | end 75 | 76 | newproperty(:push_events) do 77 | desc 'Boolean value for indicating if webhook should be triggered on a push event. GitLab Only.' 78 | 79 | def insync?(is) 80 | is.to_s == should.to_s 81 | end 82 | 83 | defaultto :false 84 | newvalues(:true, :false) 85 | end 86 | 87 | newproperty(:issues_events) do 88 | desc 'Boolean value for indicating if webhook should be triggered on an issues event. GitLab Only.' 89 | 90 | def insync?(is) 91 | is.to_s == should.to_s 92 | end 93 | 94 | defaultto :false 95 | newvalues(:true, :false) 96 | end 97 | 98 | newproperty(:merge_requests_events) do 99 | desc 'Boolean value for indicating if webhook should be triggered on a merge requests event. GitLab Only.' 100 | 101 | def insync?(is) 102 | is.to_s == should.to_s 103 | end 104 | 105 | defaultto :false 106 | newvalues(:true, :false) 107 | end 108 | 109 | newproperty(:tag_push_events) do 110 | desc 'Boolean value for indicating if webhook should be triggered on a tag push event. GitLab Only.' 111 | 112 | def insync?(is) 113 | is.to_s == should.to_s 114 | end 115 | 116 | defaultto :false 117 | newvalues(:true, :false) 118 | end 119 | 120 | newproperty(:note_events) do 121 | desc 'Boolean value for indicating if webhook should be triggered on a note event. GitLab Only.' 122 | 123 | def insync?(is) 124 | is.to_s == should.to_s 125 | end 126 | 127 | defaultto :false 128 | newvalues(:true, :false) 129 | end 130 | 131 | newproperty(:build_events) do 132 | desc 'Boolean value for indicating if webhook should be triggered on a build event. GitLab Only.' 133 | 134 | def insync?(is) 135 | is.to_s == should.to_s 136 | end 137 | 138 | defaultto :false 139 | newvalues(:true, :false) 140 | end 141 | 142 | newproperty(:pipeline_events) do 143 | desc 'Boolean value for indicating if webhook should be triggered on a pipeline event. GitLab Only.' 144 | 145 | def insync?(is) 146 | is.to_s == should.to_s 147 | end 148 | 149 | defaultto :false 150 | newvalues(:true, :false) 151 | end 152 | 153 | newproperty(:wiki_page_events) do 154 | desc 'Boolean value for indicating if webhook should be triggered on a wiki page event. GitLab Only.' 155 | 156 | def insync?(is) 157 | is.to_s == should.to_s 158 | end 159 | 160 | defaultto :false 161 | newvalues(:true, :false) 162 | end 163 | 164 | newproperty(:insecure_ssl) do 165 | desc 'Boolean value for disabling SSL verification for this webhook. Optional.' 166 | 167 | defaultto :false 168 | newvalues(:true, :false) 169 | end 170 | 171 | newproperty(:server_url) do 172 | desc 'The URL path to the Git management system server. Required.' 173 | 174 | validate do |value| 175 | unless value =~ /^(https?:\/\/).*:?.*\/?$/ 176 | raise(Puppet::Error, "Git server URL must be fully qualified, not '#{value}'") 177 | end 178 | end 179 | end 180 | 181 | read_only_properties = { 182 | id: 'id', 183 | last_response_code: 'last_response_code', 184 | last_response_status: 'last_response_status', 185 | last_response_message: 'last_response_message', 186 | updated_at: 'updated_at', 187 | created_at: 'created_at', 188 | rest_url: 'rest_url', 189 | test_url: 'test_url', 190 | ping_url: 'ping_url', 191 | } 192 | 193 | read_only_properties.each do |property, value| 194 | newproperty(property, :parent => PuppetX::Property::ReadOnly) do 195 | desc "Information related to #{value} from the GitHub v3 API." 196 | 197 | munge do |value| 198 | value.to_s 199 | end 200 | end 201 | end 202 | 203 | validate do 204 | validate_token_or_token_file 205 | end 206 | 207 | end 208 | -------------------------------------------------------------------------------- /lib/puppet/provider/git_deploy_key/stash.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'net/http' 3 | require 'json' 4 | 5 | Puppet::Type.type(:git_deploy_key).provide(:stash) do 6 | 7 | defaultfor :stash => :exists 8 | 9 | def calling_method 10 | # Get calling method and clean it up for good reporting 11 | cm = String.new 12 | cm = caller[0].split(" ").last 13 | cm.tr!('\'', '') 14 | cm.tr!('\`','') 15 | cm 16 | end 17 | 18 | def prereq_check 19 | # Check to see if all required parameters have been passed 20 | missing_params = Array.new 21 | 22 | if resource[:name].nil? 23 | missing_params << 'name' 24 | end 25 | if resource[:username].nil? 26 | missing_params << 'username' 27 | end 28 | if resource[:password].nil? 29 | missing_params << 'password' 30 | end 31 | if resource[:project_name].nil? 32 | missing_params << 'project_name' 33 | end 34 | if resource[:path].nil? 35 | missing_params << 'hook_exe' 36 | end 37 | if resource[:server_url].nil? 38 | missing_params << 'server_url' 39 | end 40 | if missing_params.size > 0 41 | raise(Puppet::Error, "stash_webhook::#{calling_method}: Must supply the git_webhook resource with required parameter(s): #{missing_params.join(', ')}") 42 | end 43 | end 44 | 45 | def gms_server 46 | # Return the prefix portion of the URL 47 | return resource[:server_url].strip unless resource[:server_url].nil? 48 | return 'http://localhost:7990' 49 | end 50 | 51 | def api_call(action,url,data = nil) 52 | # Reusable API caller method 53 | uri = URI.parse(url) 54 | 55 | http = Net::HTTP.new(uri.host, uri.port) 56 | 57 | if uri.port == 443 or uri.scheme == 'https' 58 | http.use_ssl = true 59 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 60 | else 61 | http.use_ssl = false 62 | end 63 | 64 | if Puppet[:debug] == true 65 | http.set_debug_output($stdout) 66 | end 67 | 68 | if action =~ /post/i 69 | req = Net::HTTP::Post.new(uri.request_uri) 70 | elsif action =~ /put/i 71 | req = Net::HTTP::Put.new(uri.request_uri) 72 | elsif action =~ /delete/i 73 | req = Net::HTTP::Delete.new(uri.request_uri) 74 | else 75 | req = Net::HTTP::Get.new(uri.request_uri) 76 | end 77 | 78 | req.set_content_type('application/json') 79 | req.basic_auth(resource[:username].strip, resource[:password].strip) 80 | 81 | if data 82 | req.body = data.to_json 83 | end 84 | 85 | Puppet.debug("stash_deploy_key::#{calling_method}: REST API #{req.method} Endpoint: #{uri.to_s}") 86 | Puppet.debug("stash_deploy_key::#{calling_method}: REST API #{req.method} Request: #{req.inspect}") 87 | 88 | response = http.request(req) 89 | 90 | Puppet.debug("stash_deploy_key::#{calling_method}: REST API #{req.method} Response: #{response.inspect}") 91 | 92 | response 93 | end 94 | 95 | def get_key_id 96 | # Return the deploy key ID 97 | prereq_check() 98 | 99 | url = String.new 100 | 101 | pn = resource[:project_name].strip 102 | 103 | unless resource[:repo_name].nil? 104 | rs = resource[:repo_name].strip 105 | url = "#{gms_server}/rest/keys/1.0/projects/#{pn}/repos/#{rs}/ssh" 106 | else 107 | url = "#{gms_server}/rest/keys/1.0/projects/#{pn}/ssh" 108 | end 109 | 110 | response = api_call('GET', url) 111 | 112 | raise(Puppet::Error, "stash_deploy_key::#{calling_method}: #{response.inspect}") unless response.class == Net::HTTPOK 113 | 114 | key_json = JSON.parse(response.body) 115 | 116 | # Clearly no deploy keys exist if size == 0 117 | if key_json['size'] == 0 118 | Puppet.debug("stash_deploy_key::#{calling_method}: No pre-existing deploy keys. Onto creation!") 119 | return nil 120 | else 121 | key_json['values'].each do |v| 122 | if v['key']['text'].eql?(File.read(resource[:path].strip).strip) 123 | Puppet.debug("stash_deploy_key::#{calling_method}: Found a key match for deploy key. Nothing more to do.") 124 | return v['key']['id'] 125 | end 126 | end 127 | end 128 | 129 | Puppet.debug("stash_deploy_key::#{calling_method}: Key provided with git_deploy_key resource is not a match for what is on file. Onto creation!") 130 | return nil 131 | end 132 | 133 | def exists? 134 | # Checks to see if the deploy exists on the Stash server 135 | if get_key_id.nil? 136 | return false 137 | else 138 | return true 139 | end 140 | end 141 | 142 | def create 143 | # Creates a deploy key in the Stash server project or repository referenced. 144 | pn = resource[:project_name].strip 145 | 146 | url = String.new 147 | 148 | opts = Hash.new 149 | opts['key'] = Hash.new 150 | opts['key']['text'] = File.read(resource[:path].strip).strip 151 | 152 | unless resource[:repo_name].nil? 153 | rs = resource[:repo_name].strip 154 | opts['permission'] = "REPO_READ" 155 | url = "#{gms_server}/rest/keys/1.0/projects/#{pn}/repos/#{rs}/ssh" 156 | else 157 | opts['permission'] = "PROJECT_READ" 158 | url = "#{gms_server}/rest/keys/1.0/projects/#{pn}/ssh" 159 | end 160 | 161 | response = api_call('POST', url, opts) 162 | 163 | if response.class == Net::HTTPCreated 164 | if resource[:repo_name].nil? 165 | Puppet.debug("stash_deploy_key::#{calling_method}: Successfully created deploy key \'#{resource[:name]}\' for project \'#{resource[:project_name]}\'") 166 | else 167 | Puppet.debug("stash_deploy_key::#{calling_method}: Successfully created deploy key \'#{resource[:name]}\' for repository \'#{resource[:repo_name]}\' in project \'#{resource[:project_name]}\'") 168 | end 169 | return true 170 | else 171 | raise(Puppet::Error, "stash_deploy_key::#{calling_method}: #{response.inspect}") 172 | end 173 | end 174 | 175 | def destroy 176 | # Remove the deploy key from the Stash project or repository referenced. 177 | dk_key_id = get_key_id 178 | pn = resource[:project_name].strip 179 | 180 | unless resource[:repo_name].nil? 181 | rs = resource[:repo_name].strip 182 | url = "#{gms_server}/rest/keys/1.0/projects/#{pn}/repos/#{rs}/ssh/#{dk_key_id}" 183 | else 184 | url = "#{gms_server}/rest/keys/1.0/projects/#{pn}/ssh/#{dk_key_id}" 185 | end 186 | 187 | response = api_call('DELETE', url) 188 | 189 | if response.class == Net::HTTPNoContent 190 | if resource[:repo_name].nil? 191 | Puppet.debug("stash_deploy_key::#{calling_method}: Successfully deleted deploy key \'#{resource[:name]}\' from project \'#{resource[:project_name]}\'") 192 | else 193 | Puppet.debug("stash_deploy_key::#{calling_method}: Successfully deleted deploy key \'#{resource[:name]}\' from repository \'#{resource[:repo_name]}\' in project \'#{resource[:project_name]}\'") 194 | end 195 | end 196 | end 197 | 198 | end 199 | -------------------------------------------------------------------------------- /lib/puppet/provider/gms_webhook/gitlab.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../puppet_x/puppetlabs/gms.rb' 2 | require 'puppet_x/gms/provider' 3 | require 'puppet/type/gms_webhook' 4 | require 'json' 5 | 6 | Puppet::Type.type(:gms_webhook).provide(:gitlab, :parent => PuppetX::Puppetlabs::Gms) do 7 | include PuppetX::GMS::Provider 8 | 9 | defaultfor :gitlab => :exist 10 | 11 | has_feature :id 12 | has_feature :name 13 | has_feature :rest_url 14 | has_feature :project_name 15 | has_feature :push_events 16 | has_feature :issues_events 17 | has_feature :merge_requests_events 18 | has_feature :tag_push_events 19 | has_feature :note_events 20 | has_feature :build_events 21 | has_feature :pipeline_events 22 | has_feature :wiki_events 23 | has_feature :created_at 24 | has_feature :webhook_url 25 | 26 | def self.instances 27 | Puppet.debug("def self.instances") 28 | 29 | instances = [] 30 | 31 | repos_url = "#{gms_server}/api/v3/projects" 32 | repos = get(repos_url, @token) 33 | 34 | webhooks = Array.new 35 | hooks = Hash.new 36 | 37 | repos.each do |r| 38 | 39 | # hooks_url = "#{gms_server}/projects/#{r['id']}/hooks" 40 | hooks_url = "#{gms_server}/api/v3/projects/#{r['id']}/hooks" 41 | 42 | hook_objs = get(hooks_url, @token) 43 | 44 | return [] if hook_objs.nil? 45 | 46 | # Puppet.notice("hook_objs = #{hook_objs.inspect}") 47 | 48 | hook_objs.each do |h| 49 | h['project_name'] = r['path_with_namespace'] if h.class == Hash 50 | h['repo_url'] = hooks_url + "/#{h['id']}" if h.class == Hash && !r['id'].nil? 51 | 52 | h['insecure_ssl'] = :false if h['enable_ssl_verification'] == true 53 | h['insecure_ssl'] = :true if h['enable_ssl_verification'] == false 54 | 55 | webhooks << h if h.class == Hash 56 | end 57 | 58 | end 59 | 60 | webhooks.each do |webhook| 61 | instances << new( 62 | ensure: :present, 63 | name: webhook['project_name'] + '_' + webhook['id'].to_s, 64 | id: webhook['id'].to_s, 65 | rest_url: webhook['repo_url'], 66 | project_name: webhook['project_name'], 67 | push_events: webhook['push_events'], 68 | issues_events: webhook['issues_events'], 69 | merge_requests_events: webhook['merge_requests_events'], 70 | tag_push_events: webhook['tag_push_events'], 71 | note_events: webhook['note_events'], 72 | build_events: webhook['build_events'], 73 | pipeline_events: webhook['pipeline_events'], 74 | wiki_page_events: webhook['wiki_page_events'], 75 | updated_at: webhook['updated_at'], 76 | created_at: webhook['created_at'], 77 | insecure_ssl: webhook['insecure_ssl'], 78 | webhook_url: webhook['url'] 79 | ) 80 | end 81 | 82 | $webhooks = instances if $webhooks.nil? || $webhooks.empty? 83 | 84 | instances 85 | end 86 | 87 | def self.prefetch(resources) 88 | Puppet.debug("def self.prefetch") 89 | 90 | @token = resources[resources.keys.first].value('token') 91 | 92 | $webhooks = instances 93 | 94 | resources.keys.each do |name| 95 | if provider = $webhooks.find { |wh| wh.project_name == resources[name].parameters[:project_name].value && wh.webhook_url == resources[name].parameters[:webhook_url].value } 96 | resources[name].provider = provider 97 | end 98 | end 99 | end 100 | 101 | def message(object) 102 | Puppet.debug("def message") 103 | # Allows us to pass in resources and get all the attributes out 104 | # in the form of a hash. 105 | 106 | message = object.to_hash 107 | 108 | if message[:insecure_ssl] == :true 109 | message[:enable_ssl_verification] = false 110 | elsif message[:insecure_ssl] == :false 111 | message[:enable_ssl_verification] = true 112 | end 113 | 114 | if message[:webhook_url] 115 | message[:url] = message[:webhook_url] 116 | # message.delete(:webhook) 117 | end 118 | 119 | gitlab_params = [:id, :url, :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events, :pipeline_events, :wiki_page_events, :enable_ssl_verification] 120 | message = sanitize_hash(gitlab_params, message) 121 | 122 | message.to_json 123 | end 124 | 125 | def gms_server 126 | PuppetX::Puppetlabs::Gms::gms_server 127 | end 128 | 129 | def calling_method 130 | # Get calling method and clean it up for good reporting 131 | cm = String.new 132 | cm = caller[0].split(" ").last 133 | cm.tr!('\'', '') 134 | cm.tr!('\`','') 135 | cm 136 | end 137 | 138 | def get_project_id(project_name) 139 | begin 140 | repos_url = "#{self.gms_server}/api/v3/projects" 141 | repos = PuppetX::Puppetlabs::Gms.get(repos_url, get_token) 142 | 143 | repos.each do |r| 144 | if project_name == r['path_with_namespace'] 145 | return r['id'] 146 | end 147 | end 148 | rescue Exception => e 149 | raise(Puppet::Error, "gms_gitlab_webhook::#{calling_method}: Unable to retrieve project ID given project name: #{e.message}") 150 | end 151 | end 152 | 153 | def exists? 154 | Puppet.debug("def exists #{self.id}") 155 | @property_hash[:ensure] == :present 156 | end 157 | 158 | def flush 159 | Puppet.debug("def flush") 160 | 161 | put_url = "#{gms_server}/api/v3/projects/#{get_project_id(resource[:project_name].strip)}/hooks/#{self.id}" 162 | 163 | if @property_hash != {} 164 | begin 165 | response = PuppetX::Puppetlabs::Gms.put(put_url, get_token, message(@property_hash)) 166 | 167 | if response.class != Net::HTTPOK 168 | raise(Puppet::Error, "gms_gitlab_webhook::#{calling_method}: #{response.inspect}") 169 | end 170 | rescue Exception => e 171 | raise(Puppet::Error, "gms_gitlab_webhook::#{calling_method}: #{e.message}") 172 | end 173 | 174 | return response 175 | end 176 | end 177 | 178 | def create 179 | Puppet.debug('def create') 180 | 181 | begin 182 | post_url = "#{self.gms_server}/api/v3/projects/#{get_project_id(resource[:project_name].strip)}/hooks" 183 | 184 | response = PuppetX::Puppetlabs::Gms.post(post_url, get_token, message(resource)) 185 | 186 | if response.class != Net::HTTPCreated 187 | raise(Puppet::Error, "gms_gitlab_webhook::#{calling_method}: #{response.inspect}") 188 | return false 189 | end 190 | 191 | @property_hash.clear 192 | 193 | return response 194 | rescue Exception => e 195 | raise(Puppet::Error, "gms_gitlab_webhook::#{calling_method}: #{e.message}") 196 | return false 197 | end 198 | 199 | end 200 | 201 | def destroy 202 | Puppet.debug("def destroy") 203 | 204 | unless webhook_id.nil? 205 | destroy_url = "#{gms_server}/api/v3/projects/#{get_project_id(resource[:project_name].strip)}/hooks/#{self.id}" 206 | 207 | begin 208 | response = PuppetX::Puppetlabs::Gms.delete(destroy_url, get_token) 209 | 210 | if (response.class == Net::HTTPNoContent) 211 | return true 212 | else 213 | raise(Puppet::Error, "gitlab_webhook::#{calling_method}: #{response.inspect}") 214 | end 215 | rescue Exception => e 216 | raise(Puppet::Error, "gitlab_webhook::#{calling_method}: #{e.message}") 217 | end 218 | 219 | end 220 | end 221 | 222 | mk_resource_methods 223 | 224 | end 225 | -------------------------------------------------------------------------------- /lib/puppet/provider/git_groupteam_member/gitlab.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'net/http' 3 | require 'json' 4 | require 'puppet_x/gms/provider' 5 | 6 | Puppet::Type.type(:git_groupteam_member).provide(:gitlab) do 7 | include PuppetX::GMS::Provider 8 | 9 | defaultfor :gitlab => :exists 10 | 11 | GUEST = 10 12 | REPORTER = 20 13 | DEVELOPER = 30 14 | MASTER = 40 15 | OWNER = 50 16 | 17 | def gms_server 18 | return resource[:server_url].strip unless resource[:server_url].nil? 19 | return 'https://gitlab.com' 20 | end 21 | 22 | def calling_method 23 | # Get calling method and clean it up for good reporting 24 | cm = String.new 25 | cm = caller[0].split(" ").last 26 | cm.tr!('\'', '') 27 | cm.tr!('\`','') 28 | cm 29 | end 30 | 31 | def api_call(action,url,data = nil) 32 | uri = URI.parse(url) 33 | 34 | http = Net::HTTP.new(uri.host, uri.port) 35 | 36 | if uri.port == 443 or uri.scheme == 'https' 37 | http.use_ssl = true 38 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 39 | else 40 | http.use_ssl = false 41 | end 42 | 43 | # Passing along the ability to see greater debugging output from net/http call 44 | if Puppet[:debug] == true 45 | http.set_debug_output($stdout) 46 | end 47 | 48 | if action =~ /post/i 49 | req = Net::HTTP::Post.new(uri.request_uri) 50 | elsif action =~ /put/i 51 | req = Net::HTTP::Put.new(uri.request_uri) 52 | elsif action =~ /delete/i 53 | req = Net::HTTP::Delete.new(uri.request_uri) 54 | else 55 | req = Net::HTTP::Get.new(uri.request_uri) 56 | end 57 | 58 | req.set_content_type('application/json') 59 | req.add_field('PRIVATE-TOKEN', get_token) 60 | 61 | if data 62 | req.body = data.to_json 63 | end 64 | 65 | Puppet.debug("gitlab_groupteam_member::#{calling_method}: REST API #{req.method} Endpoint: #{uri.to_s}") 66 | Puppet.debug("gitlab_groupteam_member::#{calling_method}: REST API #{req.method} Request: #{req.inspect}") 67 | 68 | response = http.request(req) 69 | 70 | Puppet.debug("gitlab_groupteam_member::#{calling_method}: REST API #{req.method} Response: #{response.inspect}") 71 | 72 | response 73 | end 74 | 75 | def exists? 76 | group_id = get_group_id 77 | 78 | groupteam_hash = Hash.new 79 | url = "#{gms_server}/api/v3//groups/#{group_id}/members" 80 | 81 | response = api_call('GET', url) 82 | 83 | groupteam_json = JSON.parse(response.body) 84 | 85 | groupteam_json.each do |child| 86 | groupteam_hash[child['username']] = child['id'] 87 | end 88 | 89 | groupteam_hash.keys.each do |k| 90 | if k.eql?(resource[:member_name].strip) 91 | Puppet.debug "gitlab_groupteam_member::#{calling_method}: Member \'#{resource[:member_name]}\' is already a member of #{resource[:groupteam_name].strip}" 92 | return true 93 | end 94 | end 95 | 96 | Puppet.debug "gitlab_groupteam_member::#{calling_method}: Member \'#{resource[:member_name]}\' is not currently a member of #{resource[:groupteam_name].strip}" 97 | return false 98 | end 99 | 100 | def get_access_level 101 | unless resource[:access_level].nil? 102 | al = resource[:access_level].strip 103 | 104 | case al 105 | when /guest/i 106 | Puppet.debug("gitlab_groupteam_member::#{calling_method}: Access Level = #{GUEST}") 107 | return GUEST 108 | when /reporter/i 109 | Puppet.debug("gitlab_groupteam_member::#{calling_method}: Access Level = #{REPORTER}") 110 | return REPORTER 111 | when /developer/i 112 | Puppet.debug("gitlab_groupteam_member::#{calling_method}: Access Level = #{DEVELOPER}") 113 | return DEVELOPER 114 | when /master/i 115 | Puppet.debug("gitlab_groupteam_member::#{calling_method}: Access Level = #{MASTER}") 116 | return MASTER 117 | when /owner/i 118 | Puppet.debug("gitlab_groupteam_member::#{calling_method}: Access Level = #{OWNER}") 119 | return OWNER 120 | else 121 | raise(Puppet::Error, "gitlab_groupteam_member::#{calling_method}: Access level provided \'#{al}\' is not GUEST, REPORTER, DEVELOPER, MASTER, or OWNER.") 122 | return nil 123 | end 124 | else 125 | raise(Puppet::Error, "gitlab_groupteam_member::#{calling_method}: Access level provided \'#{al}\' is not GUEST, REPORTER, DEVELOPER, MASTER, or OWNER.") 126 | return nil 127 | end 128 | end 129 | 130 | def get_group_id 131 | group_hash = Hash.new 132 | 133 | url = "#{gms_server}/api/v3/groups?search=#{resource[:groupteam_name].strip}" 134 | 135 | response = api_call('GET', url) 136 | 137 | group_json = JSON.parse(response.body) 138 | 139 | group_json.each do |child| 140 | group_hash[child['name']] = child['id'] 141 | end 142 | 143 | group_hash.each do |k,v| 144 | if k.eql?(resource[:groupteam_name].strip) 145 | return v.to_i 146 | end 147 | end 148 | 149 | raise(Puppet::Error, "gitlab_groupteam_member::#{calling_method}: Unable to find nonexistent group \'#{resource[:groupteam_name].strip}\'") 150 | return nil 151 | end 152 | 153 | def get_user_id 154 | group_hash = Hash.new 155 | 156 | url = "#{gms_server}/api/v3/users?search=#{resource[:member_name].strip}" 157 | 158 | response = api_call('GET', url) 159 | 160 | group_json = JSON.parse(response.body) 161 | 162 | group_json.each do |child| 163 | group_hash[child['username']] = child['id'] 164 | end 165 | 166 | group_hash.each do |k,v| 167 | if k.eql?(resource[:member_name].strip) 168 | return v.to_i 169 | end 170 | end 171 | 172 | raise(Puppet::Error, "gitlab_groupteam_member::#{calling_method}: Unable to add nonexistent user \'#{resource[:member_name].strip}\' to group #{resource[:groupteam_name].strip}") 173 | return nil 174 | end 175 | 176 | def create 177 | access_level = get_access_level 178 | group_id = get_group_id 179 | user_id = get_user_id 180 | 181 | url = "#{gms_server}/api/v3//groups/#{group_id}/members" 182 | 183 | begin 184 | if access_level.nil? || group_id.nil? || user_id.nil? 185 | raise(Puppet::Error, "gitlab_groupteam_member::#{calling_method}: Cannot add member to group with values presented: user_id => #{user_id}, group_id => #{group_id}, and access_level => #{access_level}") 186 | else 187 | opts = { 'id' => group_id, 'user_id' => user_id, 'access_level' => access_level } 188 | end 189 | 190 | Puppet.debug("gitlab_groupteam_member::#{calling_method}: opts => #{opts.inspect}") 191 | 192 | response = api_call('POST', url, opts) 193 | 194 | if (response.class == Net::HTTPCreated) 195 | return true 196 | else 197 | raise(Puppet::Error, "gitlab_groupteam_member::#{calling_method}: #{response.inspect}") 198 | end 199 | rescue Exception => e 200 | raise(Puppet::Error, "gitlab_groupteam_member::#{calling_method}: #{e.message}") 201 | end 202 | end 203 | 204 | def destroy 205 | group_id = get_group_id 206 | user_id = get_user_id 207 | 208 | unless group_id.nil? 209 | url = "#{gms_server}/api/v3//groups/#{group_id}/members/#{user_id}" 210 | 211 | begin 212 | response = api_call('DELETE', url) 213 | 214 | if response.class == Net::HTTPOK 215 | return true 216 | else 217 | raise(Puppet::Error, "gitlab_groupteam_member::#{calling_method}: #{response.inspect}") 218 | end 219 | rescue Exception => e 220 | raise(Puppet::Error, "gitlab_groupteam_member::#{calling_method}: #{e.message}") 221 | end 222 | 223 | end 224 | end 225 | 226 | end 227 | 228 | 229 | -------------------------------------------------------------------------------- /lib/puppet/provider/gms_webhook/github.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../puppet_x/puppetlabs/gms.rb' 2 | require 'puppet_x/gms/provider' 3 | require 'puppet/type/gms_webhook' 4 | require 'json' 5 | 6 | Puppet::Type.type(:gms_webhook).provide(:github, :parent => PuppetX::Puppetlabs::Gms) do 7 | include PuppetX::GMS::Provider 8 | 9 | defaultfor :github => :exist 10 | defaultfor :feature => :posix 11 | 12 | def self.instances 13 | Puppet.debug("def self.instances") 14 | 15 | instances = [] 16 | 17 | repos_url = "#{gms_server}/user/repos" 18 | repos = get(repos_url, @token) 19 | 20 | webhooks = Array.new 21 | hooks = Hash.new 22 | 23 | repos.each do |r| 24 | 25 | hooks_url = r['hooks_url'] 26 | hook_objs = get(hooks_url, @token) 27 | return [] if hook_objs.nil? 28 | 29 | hook_objs.each do |h| 30 | hooks[h['id']] = h['url'] if h.class == Hash && h[:message].nil? 31 | webhooks << h if h.class == Hash && h[:message].nil? 32 | end 33 | 34 | end 35 | 36 | webhooks.each do |webhook| 37 | 38 | if webhook['config']['insecure_ssl'] == '1' || webhook['config']['insecure_ssl'] == 'true' 39 | webhook['config']['insecure_ssl'] = :true 40 | else 41 | webhook['config']['insecure_ssl'] = :false 42 | end 43 | 44 | webhook['active'] = :true if webhook['active'] == true 45 | webhook['active'] = :false if webhook['active'] == false 46 | 47 | # Build project_name parameter 48 | pn_array = webhook['url'].strip.split('/') 49 | pn = pn_array[4] + '/' + pn_array[5] 50 | 51 | instances << new( 52 | ensure: :present, 53 | name: webhook['name'] + '_' + webhook['id'].to_s, 54 | id: webhook['id'].to_s, 55 | web: webhook['web'], 56 | rest_url: webhook['url'], 57 | test_url: webhook['test_url'], 58 | ping_url: webhook['ping_url'], 59 | project_name: pn, 60 | active: webhook['active'], 61 | events: webhook['events'], 62 | last_response_code: webhook['last_response']['code'], 63 | last_response_status: webhook['last_response']['status'], 64 | last_response_message: webhook['last_response']['message'], 65 | updated_at: webhook['updated_at'], 66 | created_at: webhook['created_at'], 67 | secret: webhook['config']['secret'], 68 | insecure_ssl: webhook['config']['insecure_ssl'], 69 | content_type: webhook['config']['content_type'], 70 | webhook_url: webhook['config']['url'] 71 | ) 72 | webhook.delete('config') 73 | end 74 | 75 | $webhooks = instances if $webhooks.nil? || $webhooks.empty? 76 | 77 | instances 78 | end 79 | 80 | def self.prefetch(resources) 81 | Puppet.debug("def self.prefetch") 82 | 83 | @token = resources[resources.keys.first].value('token') 84 | 85 | $webhooks = instances 86 | 87 | resources.keys.each do |name| 88 | if provider = $webhooks.find { |wh| wh.project_name == resources[name].parameters[:project_name].value && wh.webhook_url == resources[name].parameters[:webhook_url].value } 89 | resources[name].provider = provider 90 | # Assume the user wants the webhook to be active if they did not specify a value for the active property but ensure => true 91 | if resources[name].parameters[:ensure].value == :present && resources[name].parameters[:active].nil? 92 | resources[name][:active] = true 93 | end 94 | end 95 | end 96 | end 97 | 98 | def message(object) 99 | Puppet.debug("def message") 100 | # Allows us to pass in resources and get all the attributes out 101 | # in the form of a hash. 102 | 103 | message = object.to_hash 104 | 105 | if message[:active] == :false 106 | message[:active] = false 107 | elsif message[:active] == :absent && message[:ensure] == :present 108 | message[:active] = true 109 | else 110 | message[:active] = true 111 | end 112 | 113 | message[:content_type] = 'json' if message[:content_type].nil? 114 | 115 | message[:events] = ['push'] if message[:events].nil? 116 | 117 | if message[:insecure_ssl] == :true || message[:insecure_ssl] == true 118 | message[:insecure_ssl] = '1' 119 | else message[:insecure_ssl] == :false 120 | message[:insecure_ssl] = '0' 121 | end 122 | 123 | # For now, we will only support setting up 'web' webhooks. GitHub has a 124 | # list of many more types of webhooks that can be supported: 125 | # https://api.github.com/hooks 126 | message[:name] = 'web' 127 | 128 | config_map = { 129 | :'content_type' => :content_type, 130 | :'insecure_ssl' => :insecure_ssl, 131 | :'webhook_url' => :url, 132 | } 133 | 134 | message = nest_hash_keys(config_map, :config, message) 135 | github_params = [:name, :config, :events, :active] 136 | message = sanitize_hash(github_params, message) 137 | 138 | message.to_json 139 | end 140 | 141 | # def get_webhook_id 142 | # Puppet.debug("def get_webhook_id") 143 | # 144 | # return self.id unless self.id == :absent || self.id.empty? 145 | # 146 | # $webhooks.each do |wh| 147 | # if resource[:project_name] == wh.project_name && resource[:webhook_url] == wh.webhook_url 148 | # return wh.id 149 | # end 150 | # end 151 | # 152 | # return nil 153 | # end 154 | 155 | def gms_server 156 | PuppetX::Puppetlabs::Gms::gms_server 157 | end 158 | 159 | def calling_method 160 | # Get calling method and clean it up for good reporting 161 | cm = String.new 162 | cm = caller[0].split(" ").last 163 | cm.tr!('\'', '') 164 | cm.tr!('\`','') 165 | cm 166 | end 167 | 168 | def exists? 169 | Puppet.debug("def exists #{self.id}") 170 | @property_hash[:ensure] == :present 171 | end 172 | 173 | def flush 174 | Puppet.debug("def flush") 175 | 176 | # resource[:project_name].strip 177 | patch_url = "#{gms_server}/repos/#{self.project_name}/hooks/#{self.id}" 178 | 179 | if @property_hash != {} 180 | begin 181 | response = PuppetX::Puppetlabs::Gms.patch(patch_url, get_token, message(@property_hash)) 182 | 183 | if response.class != Net::HTTPOK 184 | raise(Puppet::Error, "github_webhook::#{calling_method}: #{response.inspect}") 185 | end 186 | rescue Exception => e 187 | raise(Puppet::Error, "github_webhook::#{calling_method}: #{e.message}") 188 | end 189 | 190 | return response 191 | end 192 | end 193 | 194 | def create 195 | Puppet.debug('def create') 196 | 197 | begin 198 | post_url = "#{self.gms_server}/repos/#{resource[:project_name].strip}/hooks" 199 | 200 | response = PuppetX::Puppetlabs::Gms.post(post_url, get_token, message(resource)) 201 | 202 | if response.class != Net::HTTPCreated 203 | raise(Puppet::Error, "gms_github_webhook::#{calling_method}: #{response.inspect}") 204 | return false 205 | end 206 | 207 | @property_hash.clear 208 | 209 | return response 210 | rescue Exception => e 211 | raise(Puppet::Error, "gms_github_webhook::#{calling_method}: #{e.message}") 212 | return false 213 | end 214 | 215 | end 216 | 217 | def destroy 218 | Puppet.debug("def destroy") 219 | 220 | unless webhook_id.nil? 221 | destroy_url = "#{gms_server}/repos/#{resource[:project_name].strip}/hooks/#{self.id}" 222 | 223 | begin 224 | response = PuppetX::Puppetlabs::Gms.delete(destroy_url, get_token) 225 | 226 | if (response.class == Net::HTTPNoContent) 227 | return true 228 | else 229 | raise(Puppet::Error, "github_webhook::#{calling_method}: #{response.inspect}") 230 | end 231 | rescue Exception => e 232 | raise(Puppet::Error, "github_webhook::#{calling_method}: #{e.message}") 233 | end 234 | 235 | end 236 | end 237 | 238 | mk_resource_methods 239 | 240 | end 241 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/gms.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'json' 3 | 4 | module PuppetX 5 | module Puppetlabs 6 | 7 | class Gms < Puppet::Provider 8 | 9 | def self.gms_server 10 | # Provide the host and port portion of the URL to calling methods 11 | provider = cm(caller[0]) 12 | return 'https://api.github.com' if provider =~ /github/i 13 | return 'https://gitlab.com' if provider =~ /gitlab/i 14 | return 'http://localhost:7990' if provider =~ /stash/i 15 | end 16 | 17 | def self.cm(method) 18 | cm = method.split("/").last 19 | cm = cm.split('.').first 20 | end 21 | 22 | def self.calling_method 23 | # Get calling method and clean it up for good reporting 24 | cm = String.new 25 | cm = caller[0].split(" ").last 26 | cm.tr!('\'', '') 27 | cm.tr!('\`','') 28 | cm 29 | end 30 | 31 | def gms_server 32 | self.class.gms_server 33 | end 34 | 35 | def calling_method 36 | self.class.calling_method 37 | end 38 | 39 | def rest_call(action, url, token, provider, data) 40 | self.class.rest_call(action, url, token, provider, data) 41 | end 42 | 43 | def self.provider(cm) 44 | cm.split('/').last.split('.').first 45 | end 46 | 47 | def self.post(url, token, data=nil) 48 | if url =~ URI::regexp 49 | begin 50 | self.rest_call('POST', url, token, provider(caller[0]), data) 51 | rescue Exception => e 52 | fail("puppet_x::puppetlabs::gms.post: Error caught on POST: #{e}") 53 | end 54 | else 55 | fail("puppet_x::puppetlabs::gms.post: Must supply a valid URL including GMS server hostname/address.") 56 | end 57 | end 58 | 59 | def self.put(url, token, data=nil) 60 | if url =~ URI::regexp 61 | begin 62 | self.rest_call('PUT', url, token, provider(caller[0]), data) 63 | rescue Exception => e 64 | fail("puppet_x::puppetlabs::gms.put: Error caught on PUT: #{e}") 65 | end 66 | else 67 | fail("puppet_x::puppetlabs::gms.put: Must supply a valid URL including GMS server hostname/address.") 68 | end 69 | end 70 | 71 | def self.patch(url, token, data=nil) 72 | if url =~ URI::regexp 73 | begin 74 | self.rest_call('PATCH', url, token, provider(caller[0]), data) 75 | rescue Exception => e 76 | fail("puppet_x::puppetlabs::gms.put: Error caught on PATCH: #{e}") 77 | end 78 | else 79 | fail("puppet_x::puppetlabs::gms.patch: Must supply a valid URL including GMS server hostname/address.") 80 | end 81 | end 82 | 83 | def self.delete(url, token, data=nil) 84 | if url =~ URI::regexp 85 | begin 86 | self.rest_call('DELETE', url, token, provider(caller[0]), data) 87 | rescue Exception => e 88 | fail("puppet_x::puppetlabs::gms.delete: Error caught on DELETE: #{e}") 89 | end 90 | else 91 | fail("puppet_x::puppetlabs::gms.delete: Must supply a valid URL including GMS server hostname/address.") 92 | end 93 | end 94 | 95 | def self.get(url, token, data=nil) 96 | if url =~ URI::regexp 97 | begin 98 | self.rest_call('GET', url, token, provider(caller[0]), data) 99 | rescue Exception => e 100 | fail("puppet_x::puppetlabs::gms.get: Error caught on GET: #{e}") 101 | end 102 | else 103 | fail("puppet_x::puppetlabs::gms.get: Must supply a valid URL including GMS server hostname/address.") 104 | end 105 | end 106 | 107 | def self.rest_call(action, url, token, provider, data) 108 | # Single method to make all calls to the respective RESTful API 109 | 110 | uri = URI.parse(url) 111 | 112 | http = Net::HTTP.new(uri.host, uri.port) 113 | 114 | if uri.port == 443 or uri.scheme == 'https' 115 | http.use_ssl = true 116 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 117 | else 118 | http.use_ssl = false 119 | end 120 | 121 | if Puppet[:debug] == true 122 | http.set_debug_output($stdout) 123 | end 124 | 125 | if action =~ /post/i 126 | req = Net::HTTP::Post.new(uri.request_uri) 127 | elsif action =~ /patch/i 128 | req = Net::HTTP::Patch.new(uri.request_uri) 129 | elsif action =~ /put/i 130 | req = Net::HTTP::Put.new(uri.request_uri) 131 | elsif action =~ /delete/i 132 | req = Net::HTTP::Delete.new(uri.request_uri) 133 | else 134 | req = Net::HTTP::Get.new(uri.request_uri) 135 | end 136 | 137 | if provider =~ /github/i 138 | 139 | req.initialize_http_header({'Accept' => 'application/vnd.github.v3+json', 'User-Agent' => 'puppet-gms'}) 140 | req.set_content_type('application/json') 141 | 142 | if token && ! token.empty? 143 | req.add_field('Authorization', "token #{token}") 144 | elsif @token_file && ! @token_file.empty? && File.exist?(@token_file) 145 | req.add_field('Authorization', "token #{File.read(@token_file).strip}") 146 | elsif ENV['GMS_TOKEN'] 147 | req.add_field('Authorization', "token #{ENV['GMS_TOKEN'].strip}") 148 | else 149 | fail("puppet_x::puppetlabs::gms: Must supply GMS_TOKEN environment variable.") 150 | end 151 | 152 | elsif provider =~ /gitlab/i 153 | req.set_content_type('application/json') 154 | req.add_field('PRIVATE-TOKEN', ENV['GMS_TOKEN'].strip) 155 | elsif provider =~ /stash/i 156 | req.set_content_type('application/json') 157 | req.basic_auth(resource[:username].strip, ENV['GMS_TOKEN'].strip) 158 | end 159 | 160 | req.body = data if data && valid_json?(data) 161 | 162 | Puppet.debug("gms_webhook::#{calling_method}: REST API #{req.method} Endpoint: #{uri.to_s}") 163 | Puppet.debug("gms_webhook::#{calling_method}: REST API #{req.method} Request: #{req.inspect}") 164 | 165 | response = http.request(req) 166 | 167 | Puppet.debug("gms_webhook::#{calling_method}: REST API #{req.method} Response: #{response.inspect}") 168 | 169 | if req.method == 'GET' 170 | return JSON.parse(response.body) 171 | else 172 | return response 173 | end 174 | end 175 | 176 | def self.url_to_project_name(url) 177 | pn_array = url.split('/') 178 | pn = pn_array[4] + '/' + pn_array[5] 179 | return pn 180 | end 181 | 182 | def sanitize_hash(params, input_hash) 183 | input_hash.each_key do |key| 184 | input_hash.delete(key) unless params.include?(key) 185 | end 186 | 187 | return input_hash 188 | end 189 | 190 | def nest_hash_keys(keys_to_rename, nest_hash, rename_hash) 191 | rename_hash[nest_hash] = Hash.new if rename_hash[nest_hash].nil? 192 | 193 | keys_to_rename.each do |k, v| 194 | next if rename_hash[nest_hash][k] 195 | value = rename_hash[k] 196 | rename_hash.delete(k) 197 | if value 198 | rename_hash[nest_hash][v] = value unless rename_hash[nest_hash][v] == value 199 | end 200 | end 201 | 202 | return rename_hash 203 | end 204 | 205 | def rename_keys(keys_to_rename, rename_hash) 206 | keys_to_rename.each do |k, v| 207 | next unless rename_hash[k] 208 | value = rename_hash[k] 209 | rename_hash.delete(k) 210 | rename_hash[v] = value 211 | end 212 | return rename_hash 213 | end 214 | 215 | def self.valid_json?(json) 216 | JSON.parse(json) 217 | return true 218 | rescue Exception => e 219 | fail("gms_webhook::#{calling_method}: Unable to parse parameters passed in from gms_webhook module as valid JSON: #{e.message}") 220 | return false 221 | end 222 | 223 | end 224 | end 225 | end 226 | -------------------------------------------------------------------------------- /lib/puppet/provider/git_webhook/stash.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'net/http' 3 | require 'json' 4 | #require 'base64' 5 | 6 | Puppet::Type.type(:git_webhook).provide(:stash) do 7 | 8 | defaultfor :stash => :exists 9 | 10 | def calling_method 11 | # Get calling method and clean it up for good reporting 12 | cm = String.new 13 | cm = caller[0].split(" ").last 14 | cm.tr!('\'', '') 15 | cm.tr!('\`','') 16 | cm 17 | end 18 | 19 | def prereq_check 20 | # Check to see if all required parameters have been passed 21 | missing_params = Array.new 22 | 23 | if resource[:name].nil? 24 | missing_params << 'name' 25 | end 26 | if resource[:username].nil? 27 | missing_params << 'username' 28 | end 29 | if resource[:password].nil? 30 | missing_params << 'password' 31 | end 32 | if resource[:project_name].nil? 33 | missing_params << 'project_name' 34 | end 35 | if resource[:repo_name].nil? 36 | missing_params << 'repo_name' 37 | end 38 | if resource[:hook_exe].nil? 39 | missing_params << 'hook_exe' 40 | end 41 | if resource[:server_url].nil? 42 | missing_params << 'server_url' 43 | end 44 | if missing_params.size > 0 45 | raise(Puppet::Error, "stash_webhook::#{calling_method}: Must supply the git_webhook resource with required parameter(s): #{missing_params.join(', ')}") 46 | end 47 | end 48 | 49 | def gms_server 50 | return resource[:server_url].strip unless resource[:server_url].nil? 51 | return 'http://localhost:7990' 52 | end 53 | 54 | def api_call(action,url,data = nil) 55 | # Reusable API caller method 56 | uri = URI.parse(url) 57 | 58 | http = Net::HTTP.new(uri.host, uri.port) 59 | 60 | if uri.port == 443 or uri.scheme == 'https' 61 | http.use_ssl = true 62 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 63 | else 64 | http.use_ssl = false 65 | end 66 | 67 | if Puppet[:debug] == true 68 | http.set_debug_output($stdout) 69 | end 70 | 71 | if action =~ /post/i 72 | req = Net::HTTP::Post.new(uri.request_uri) 73 | elsif action =~ /put/i 74 | req = Net::HTTP::Put.new(uri.request_uri) 75 | elsif action =~ /delete/i 76 | req = Net::HTTP::Delete.new(uri.request_uri) 77 | else 78 | req = Net::HTTP::Get.new(uri.request_uri) 79 | end 80 | 81 | req.set_content_type('application/json') 82 | req.basic_auth(resource[:username].strip, resource[:password].strip) 83 | 84 | if data 85 | req.body = data.to_json 86 | end 87 | 88 | Puppet.debug("stash_webhook::#{calling_method}: REST API #{req.method} Endpoint: #{uri.to_s}") 89 | Puppet.debug("stash_webhook::#{calling_method}: REST API #{req.method} Request: #{req.inspect}") 90 | 91 | response = http.request(req) 92 | 93 | Puppet.debug("stash_webhook::#{calling_method}: REST API #{req.method} Response: #{response.inspect}") 94 | 95 | response 96 | end 97 | 98 | def exists? 99 | # Checks to see if the webhook exists on the Stash server 100 | prereq_check() 101 | 102 | pn = resource[:project_name].strip 103 | rs = resource[:repo_name].strip 104 | 105 | webhook_hash = Hash.new 106 | url = "#{gms_server}/rest/api/1.0/projects/#{pn}/repos/#{rs}/settings/hooks" 107 | 108 | response = api_call('GET', url) 109 | 110 | webhook_json = JSON.parse(response.body) 111 | 112 | webhook_json.each do |children| 113 | children.each do |child| 114 | if child.class == Array 115 | child.each do |hook| 116 | if hook['details'].key?('key') && hook['details']['key'] == 'com.ngs.stash.externalhooks.external-hooks:external-post-receive-hook' #&& hook['configured'] == true 117 | url = "#{gms_server}/rest/api/1.0/projects/#{pn}/repos/#{rs}/settings/hooks/com.ngs.stash.externalhooks.external-hooks:external-post-receive-hook/settings" 118 | Puppet.debug "stash_webhook::#{calling_method}: External post receive commit hook exists, checking for similarities..." 119 | response = api_call('GET', url) 120 | 121 | if response.class == Net::HTTPNoContent 122 | Puppet.debug("stash_webhook::#{calling_method}: External post receive hook is not configured.") 123 | return false 124 | end 125 | 126 | exe_json = JSON.parse(response.body) 127 | 128 | resource[:hook_exe] = '' if resource[:hook_exe].nil? 129 | resource[:hook_exe_params] = '' if resource[:hook_exe_params].nil? 130 | 131 | exe_check = nil 132 | params_check = nil 133 | 134 | if exe_json['exe'] == resource[:hook_exe].strip 135 | exe_check = true 136 | else 137 | exe_check = false 138 | end 139 | 140 | if exe_json['params'] == resource[:hook_exe_params].strip 141 | params_check = true 142 | else 143 | params_check = false 144 | end 145 | 146 | if hook.key?('enabled') 147 | if resource[:ensure].to_s == 'present' && hook['enabled'] == false 148 | return false 149 | elsif resource[:ensure].to_s == 'absent' && hook['enabled'] == true 150 | return true 151 | elsif resource[:ensure] == :present && hook['enabled'] == true 152 | return true 153 | else 154 | return false 155 | end 156 | end 157 | 158 | if exe_check && params_check 159 | Puppet.debug "stash_webhook::#{calling_method}: Confirmed external post receive hook exists in defined state already." 160 | return true 161 | else 162 | Puppet.debug "stash_webhook::#{calling_method}: External post receive hook has differing data from parameters passed in resource." 163 | return false 164 | end 165 | end 166 | end 167 | end 168 | end 169 | end 170 | 171 | raise(Puppet::Error, "stash_webhook::#{calling_method}: External Async Post Receive Hook does not appear to be installed on your Stash server. This is required for the git_webhook resource to work on Stash servers.") 172 | end 173 | 174 | def create 175 | # Creates a webhook in the Stash repository referenced. 176 | pn = resource[:project_name].strip 177 | rs = resource[:repo_name].strip 178 | 179 | url = "#{gms_server}/rest/api/1.0/projects/#{pn}/repos/#{rs}/settings/hooks/com.ngs.stash.externalhooks.external-hooks:external-post-receive-hook/settings" 180 | 181 | begin 182 | opts = Hash.new 183 | 184 | unless resource[:hook_exe_params].nil? 185 | opts = { 'exe' => resource[:hook_exe].strip, 'params' => resource[:hook_exe_params].strip } 186 | else 187 | opts = { 'exe' => resource[:hook_exe].strip } 188 | end 189 | 190 | response = api_call('PUT', url, opts) 191 | 192 | raise(Puppet::Error, "stash_webhook::#{calling_method}: #{response.inspect}") if (response.class != Net::HTTPOK) 193 | rescue Exception => e 194 | raise(Puppet::Error, "stash_webhook::#{calling_method}: #{e.message}") 195 | end 196 | 197 | enable() 198 | end 199 | 200 | def enable 201 | # Enabled a webhook in the Stash repository referenced. 202 | pn = resource[:project_name].strip 203 | rs = resource[:repo_name].strip 204 | 205 | begin 206 | url = "#{gms_server}/rest/api/1.0/projects/#{pn}/repos/#{rs}/settings/hooks/com.ngs.stash.externalhooks.external-hooks:external-post-receive-hook/enabled" 207 | 208 | response = api_call('PUT', url) 209 | 210 | if response.class == Net::HTTPOK 211 | Puppet.debug "stash_webhook::#{calling_method}: External post receive hook now enabled." 212 | return true 213 | else 214 | raise(Puppet::Error, "stash_webhook::#{calling_method}: #{response.inspect}") 215 | end 216 | rescue Exception => e 217 | raise(Puppet::Error, e.message) 218 | end 219 | end 220 | 221 | def disable 222 | # Disable a webhook in the Stash repository referenced. 223 | pn = resource[:project_name].strip 224 | rs = resource[:repo_name].strip 225 | 226 | begin 227 | url = "#{gms_server}/rest/api/1.0/projects/#{pn}/repos/#{rs}/settings/hooks/com.ngs.stash.externalhooks.external-hooks:external-post-receive-hook/enabled" 228 | 229 | response = api_call('DELETE', url) 230 | 231 | if response.class == Net::HTTPOK 232 | Puppet.debug "stash_webhook::#{calling_method}: External post receive hook now disabled." 233 | return true 234 | else 235 | raise(Puppet::Error, "stash_webhook::#{calling_method}: #{response.inspect}") 236 | end 237 | rescue Exception => e 238 | raise(Puppet::Error, e.message) 239 | end 240 | end 241 | 242 | def destroy 243 | # Renders the webhook unusable. 244 | disable() 245 | end 246 | 247 | end 248 | 249 | 250 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Puppet 2 | Forge](http://img.shields.io/puppetforge/v/abrader/gms.svg)](https://forge.puppetlabs.com/abrader/gms) 3 | [![Build 4 | Status](https://travis-ci.org/abrader/abrader-gms.svg?branch=master)](https://travis-ci.org/abrader/abrader-gms) 5 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/abrader/abrader-gms?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 6 | [![Puppet Forge Downloads](http://img.shields.io/puppetforge/dt/abrader/gms.svg)](https://forge.puppetlabs.com/abrader/gms) 7 | 8 | # Git Management Systems API Types & Providers 9 | 10 | As of right now this repository only covers the following GMS functionality: 11 | 12 | ## API functions covered 13 | 14 | |Function|GitHub|GitLab|Stash| 15 | |--------|------|------|-----| 16 | |git_deploy_key|X|X|X| 17 | |git_webhook|X|X|X| 18 | 19 | Of course it is our intent to provide more coverage of the respective APIs in the future. Please feel free to submit PRs as well. 20 | 21 | ## Permissions to use API 22 | 23 | The following is a table indicating the necessary level of permission needed for the user the authenticating credential(s) are associated with: 24 | 25 | |Function|GitHub|GitLab|Stash| 26 | |--------|------|------|-----| 27 | |git_deploy_key|owners|master|repo_admin| 28 | |git_webhook|owners|master|repo_admin| 29 | 30 | ## Debugging 31 | 32 | Troubleshooting issues when APIs are involved can be painful. Now the advertised providers within this module can pass you useful debugging info when you append the debug argument to your puppet run: 33 | 34 | ```bash 35 | puppet apply --debug 36 | ``` 37 | or 38 | ```bash 39 | puppet agent --debug 40 | ``` 41 | 42 | ## git_deploy_key 43 | 44 | A deploy key is an SSH key that is stored on your server and grants access to a single GitHub repository. This key is attached directly to the repository instead of to a personal user account. Anyone with access to the repository and server has the ability to deploy the project. It is also beneficial for users since they are not required to change their local SSH settings. 45 | 46 | ### GMS agnostic mandatory parameters 47 | 48 | #### ensure 49 | 50 | Add or remove the deploy key from the GMS 51 | 52 | ```puppet 53 | ensure => present, 54 | ``` 55 | or 56 | ```puppet 57 | ensure => absent, 58 | ``` 59 | 60 | #### path 61 | The file Puppet will ensure is provided to the prefered Git management system 62 | 63 | ```puppet 64 | path => '/root/.ssh/id_dsa.pub', 65 | ``` 66 | 67 | #### project_name 68 | The project name associated with the project 69 | 70 | Be sure to follow the 'userid/repo' format to insure proper operation for GitHub & GitLab. For Stash, only include the project name for this parameter. 71 | 72 | ```puppet 73 | project_name => 'abrader/abrader-gms', 74 | ``` 75 | 76 | #### server_url 77 | The URL path to the Git management system server 78 | 79 | Both http & https URLs are acceptable. 80 | 81 | ```puppet 82 | server_url => 'http://my.internal.gms.server.example.com', 83 | ``` 84 | 85 | If using GitHub Enterprise, add `/api/v3` to the URL, and you'll probably need to use https: 86 | 87 | ```puppet 88 | server_url => 'https://my.internal.gms.server.example.com/api/v3', 89 | ``` 90 | 91 | #### provider 92 | 93 | The Git Management System you are currently using in reference to the webhook you are managing. Currently only GitHub and GitLab are supported. 94 | 95 | ```puppet 96 | provider => 'github', 97 | ``` 98 | or 99 | ```puppet 100 | provider => 'gitlab', 101 | ``` 102 | or 103 | ```puppet 104 | provider => 'stash', 105 | ``` 106 | 107 | #### name 108 | A unique title for the key that will be provided to the prefered Git management system. This parameter is namevar. 109 | 110 | ```puppet 111 | name => 'One of my unique deploy keys', 112 | ``` 113 | 114 | ### GitHub & GitLab mandatory authentication parameter 115 | 116 | GitHub and GitLab utilize a token based authentication system to access their APIs respectively 117 | 118 | The API token generated must have admin permissions and the ability to read/write keys. If the permissions are wrong, you'll likely see the following error message: 119 | 120 | ``` 121 | ...can't convert String into Integer... 122 | ``` 123 | 124 | #### token 125 | This is the unique token you created within your GMS to allow you to interface with the system via the API. 126 | 127 | ```puppet 128 | token => 'ABCDEF1234568', 129 | ``` 130 | 131 | #### token\_file 132 | The path to a file containing the unique token you created within your GMS to allow you to interface with the system via the API. This is an alternative to, and is mutually exlusive to use of the `token` parameter. 133 | 134 | ```puppet 135 | token_file => '/etc/gitlab/api-token', 136 | ``` 137 | 138 | ### Stash mandatory authentication parameters 139 | 140 | Stash utilizes a Basic Authentication system as well as an OAuth system for accessing their API respectively. Since OAuth requires a callback URL based system that can not be feasibly implemented by this GMS module, only Basic Authenticaiton is supported. 141 | 142 | #### username 143 | This is the unique token you created within your GMS to allow you to interface with the system via the API. 144 | 145 | ```puppet 146 | username => 'ihavealotof', 147 | ``` 148 | 149 | #### password 150 | This is the unique token you created within your GMS to allow you to interface with the system via the API. 151 | 152 | ```puppet 153 | password => 'puppet_love', 154 | ``` 155 | 156 | ### Stash optional parameter 157 | 158 | Stash allows a deploy key to be associated with a project ([project_name](#project_name)) or with a repository ([repo_name](#repo_name)). By choosing to omit the repo_name parameter, this module will assume you are associating the SSH key in your git_deploy_key resource block with the project. 159 | 160 | #### repo\_name 161 | 162 | ```puppet 163 | repo_name => 'control', 164 | ``` 165 | 166 | ### A GitHub & GitLab deploy key example 167 | 168 | ```puppet 169 | git_deploy_key { 'add_deploy_key_to_puppet_control': 170 | ensure => present, 171 | name => $::fqdn, 172 | path => '/root/.ssh/id_dsa.pub', 173 | token => hiera('gitlab_api_token'), 174 | project_name => 'puppet/control', 175 | server_url => 'http://your.internal.github.server.com', 176 | provider => 'github', 177 | } 178 | ``` 179 | 180 | ### A Stash deploy key example 181 | 182 | The example below utilizes the optional [repo_name](#repo_name) parameter to ensure the SSH key in git_deploy_key resouce block below is associated with the repository and not the parent project. 183 | 184 | ```puppet 185 | git_deploy_key { 'magical stash deploy key' : 186 | ensure => present, 187 | name => $::fqdn, 188 | username => hiera('stash_api_username'), 189 | password => hiera('stash_api_password'), 190 | project_name => 'puppet', 191 | repo_name => 'control', 192 | path => '/root/.ssh/id_rsa.pub', 193 | server_url => 'http://your.internal.stash.server.com:7990', 194 | provider => 'stash', 195 | } 196 | ``` 197 | 198 | -- 199 | 200 | ## git\_webhook 201 | 202 | A webhook allows repository admins to manage the post-receive hooks for a repository. Very helpful in the case you have many Puppet masters you manage and therefore are responsible for their respective webhooks. This is refers only to respository webhooks and not organizational webhook as offered by Github. If that functionality is ever supported by this project it will be identified separately. 203 | 204 | ### GMS system agnostic mandatory parameters 205 | 206 | #### ensure 207 | 208 | Add or remove the deploy key from the GMS 209 | 210 | ```puppet 211 | ensure => present, 212 | ``` 213 | or 214 | ```puppet 215 | ensure => absent, 216 | ``` 217 | 218 | #### name 219 | 220 | A unique title for the key that will be provided to the prefered Git management system. This parameter is namevar. 221 | 222 | ```puppet 223 | name => 'super_unique_name_for_webhook', 224 | ``` 225 | 226 | #### provider 227 | 228 | The Git Management System you are currently using in reference to the webhook you are managing. Currently only GitHub and GitLab are supported. 229 | 230 | ```puppet 231 | provider => 'github', 232 | ``` 233 | or 234 | ```puppet 235 | provider => 'gitlab', 236 | ``` 237 | or 238 | ```puppet 239 | provider => 'stash', 240 | ``` 241 | 242 | #### webhook\_url 243 | 244 | The URL relating to the webhook. This typically has payload in the name. 245 | 246 | ```puppet 247 | webhook_url => 'https://puppetmaster.example.com:8088/payload', 248 | ``` 249 | 250 | #### token 251 | This is the unique token you created within your GMS to allow you to interface with the system via the API. 252 | 253 | ```puppet 254 | token => 'ABCDEF1234568', 255 | ``` 256 | 257 | #### token\_file 258 | The path to a file containing the unique token you created within your GMS to allow you to interface with the system via the API. This is an alternative to, and is mutually exlusive to use of the `token` parameter. 259 | 260 | ```puppet 261 | token_file => '/etc/gitlab/api-token', 262 | ``` 263 | 264 | #### project\_name 265 | The project name associated with the project 266 | 267 | Be sure to follow the 'userid/repo' format to insure proper operation for GitHub & GitLab. For Stash, only include the project name for this parameter. 268 | 269 | ```puppet 270 | project_name => 'control', 271 | ``` 272 | 273 | #### server\_url 274 | The URL path to the Git management system server 275 | 276 | Both http & https URLs are acceptable. 277 | 278 | ```puppet 279 | server_url => 'http://my.internal.gms.server.example.com', 280 | ``` 281 | 282 | ### GitHub & GitLab mandatory authentication parameter 283 | 284 | GitHub and GitLab utilize a token based authentication system to access their APIs respectively 285 | 286 | #### token 287 | This is the unique token you created within your GMS to allow you to interface with the system via the API. 288 | 289 | ```puppet 290 | token => 'ABCDEF1234568', 291 | ``` 292 | 293 | #### token\_file 294 | The path to a file containing the unique token you created within your GMS to allow you to interface with the system via the API. This is an alternative to, and is mutually exlusive to use of the `token` parameter. 295 | 296 | ```puppet 297 | token_file => '/etc/gitlab/api-token', 298 | ``` 299 | 300 | ### Stash mandatory authentication parameters 301 | 302 | Stash utilizes a Basic Authentication system as well as an OAuth system for accessing their API respectively. Since OAuth requires a callback URL based system that can not be feasibly implemented by this GMS module, only Basic Authenticaiton is supported. 303 | 304 | #### username 305 | This is the unique token you created within your GMS to allow you to interface with the system via the API. 306 | 307 | ```puppet 308 | username => 'ihavealotof', 309 | ``` 310 | 311 | #### password 312 | This is the unique token you created within your GMS to allow you to interface with the system via the API. 313 | 314 | ```puppet 315 | password => 'puppet_love', 316 | ``` 317 | 318 | ### Stash mandatory parameter 319 | 320 | Stash allows a deploy key to be associated with a project ([project_name](#project_name)) or with a repository ([repo_name](#repo_name)). By choosing to omit the repo_name parameter, this module will assume you are associating the SSH key in your git_deploy_key resource block with the project. 321 | 322 | #### repo\_name 323 | 324 | The name of the repository associated 325 | 326 | ```puppet 327 | repo_name => 'control', 328 | ``` 329 | 330 | ### GitHub & Gitlab optional parameters 331 | 332 | #### disable\_ssl\_verify 333 | Boolean value for disabling SSL verification for this webhook. **NOTE: Does not work on Stash ** 334 | 335 | ```puppet 336 | disable_ssl_verify => true, 337 | ``` 338 | 339 | The gitlab provider sets `enable_ssl_verification` to false when this attribute is used 340 | 341 | ### GitLab optional Parameters 342 | 343 | #### merge\_request\_events 344 | The URL in the webhook_url parameter will be triggered when a merge requests event occurs. **NOTE: GitLab only** 345 | 346 | ```puppet 347 | merge_request_events => true, 348 | ``` 349 | 350 | #### tag\_push_events 351 | The URL in the webhook_url parameter will be triggered when a tag push event occurs. **NOTE: GitLab only** 352 | 353 | ```puppet 354 | tag_push_events => true, 355 | ``` 356 | 357 | #### issue_events 358 | The URL in the webhook_url parameter will be triggered when an issues event occurs. **NOTE: GitLab only** 359 | 360 | ```puppet 361 | issue_events => true, 362 | ``` 363 | 364 | ### GitHub webhook example 365 | 366 | ```puppet 367 | git_webhook { 'web_post_receive_webhook' : 368 | ensure => present, 369 | webhook_url => 'https://puppetmaster.example.com:8088/payload', 370 | token => hiera('gitlab_api_token'), 371 | project_name => 'puppet/control', 372 | server_url => 'http://your.internal.github.server.com', 373 | disable_ssl_verify => true, 374 | provider => 'github', 375 | } 376 | ``` 377 | 378 | ### GitLab webhook example 379 | 380 | ```puppet 381 | git_webhook { 'web_post_receive_webhook' : 382 | ensure => present, 383 | webhook_url => 'https://puppetmaster.example.com:8088/payload', 384 | token => hiera('gitlab_api_token'), 385 | merge_request_events => true, 386 | project_name => 'puppet/control', 387 | server_url => 'http://your.internal.gitlab.server.com', 388 | provider => 'gitlab', 389 | } 390 | ``` 391 | 392 | ### Stash webhook example 393 | 394 | ```puppet 395 | git_webhook { 'web_post_receive_webhook' : 396 | ensure => present, 397 | webhook_url => 'https://puppetmaster.example.com:8088/payload', 398 | username => hiera('stash_api_username'), 399 | password => hiera('stash_api_password'), 400 | project_name => 'puppet', 401 | repo_name => 'control', 402 | server_url => 'http://your.internal.stash.server.com:7990', 403 | provider => 'stash', 404 | } 405 | ``` 406 | 407 | ## Limited use access tokens (GitHub only) 408 | 409 | By heading over the following link: 410 | 411 | [Create a GitHub Access Token](https://github.com/settings/tokens/new) 412 | 413 | You should see a screen that resembles something like the following image: 414 | 415 | ![alt text](https://github.com/abrader/abrader-gms/raw/master/github_new_token.png "GitHub Access Token") 416 | 417 | By highlighting **only** the following options: 418 | 419 | * write:repo_hook 420 | * read:repo_hook 421 | * admin:repo_hook 422 | 423 | You are limiting this token to only be able to manage webhooks. This may be very beneficial to you if the current tokens available to you entitle too much access. Ultimately, you are puppetizing webhook creation, limiting scope of the token capability only makes sense. 424 | 425 | 426 | 427 | -- 428 | --------------------------------------------------------------------------------