Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Puppet vs. Jenkins: A Tale of Types and Providers

Puppet vs. Jenkins: A Tale of Types and Providers

PuppetConf 2015

Avatar for Joshua Hoblitt

Joshua Hoblitt

October 08, 2015

More Decks by Joshua Hoblitt

Other Decks in Technology

Transcript

  1. Puppet vs. Jenkins: A Tale of Types and Providers Joshua

    Hoblitt DevOps Engineer | Large Synoptic Survey Telescope @jhoblitt
  2. Puppet & Me • Puppet user since 2010 – 0.25

    • 30 modules published the forge – 2 Puppetlabs “Approved” • PRs merged into ~37 puppet related repos on github
  3. Index • Prologue • Jenkins • Puppet Disclaimer: this talk

    assumes an understanding of implementing types and providers
  4. “Config” file format <securityRealm class="hudson.security.HudsonPrivateSecurityRealm"> <disableSignup>false</disableSignup> <enableCaptcha>false</enableCaptcha> </securityRealm> private final

    boolean disableSignup; private final boolean enableCaptcha; public HudsonPrivateSecurityRealm(boolean allowsSignup, boolean enableCaptcha, CaptchaSupport captchaSupport) { this.disableSignup = !allowsSignup; this.enableCaptcha = enableCaptcha; setCaptchaSupport(captchaSupport); ... } Example constructor Generated XML fragment
  5. CLI HTTP X-Jenkins-CLI2-Port: 44444 SSH $ ssh ­p 34512 admin@localhost

    ­i ~/id_rsa groovysh Groovy Shell (1.8.9, JVM: 1.7.0_85) Type 'help' or '\h' for help. ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ groovy:000>
  6. CLI groovy • Allows groovy scripts to be run on

    jenkins' JVM • Maps local stdin/stdout to groovy interpreter over CLI connection • Passes additional CLI args to groovy interpreter • Means of working around CLI command limitations $ java ­jar jenkins­cli.jar ­s http://localhost:8080 ­i id_rsa\ groovy puppet_helper.groovy get_num_executors 42 $ java ­jar jenkins­cli.jar ­s http://localhost:8080 ­i id_rsa\ groovy puppet_helper.groovy set_num_executors 3 $ java ­jar jenkins­cli.jar ­s http://localhost:8080 ­i id_rsa\ groovy puppet_helper.groovy get_num_executors 3
  7. CLI or Authentication $ java ­jar jenkins­cli.jar ­s http:/localhost:8080 copy­job

    test1 test2 hudson.security.AccessDeniedException2: anonymous is missing the Job/Create permission at hudson.security.ACL.checkPermission(ACL.java:63) … $ java ­jar jenkins­cli.jar ­s http://localhost:8080 list­jobs test1 $ java ­jar jenkins­cli.jar ­i ~/id_rsa ­s http://localhost:8080 copy­job test1 test2 java.io.EOFException at java.io.DataInputStream.readBoolean(DataInputStream.java:244) … $ java ­jar jenkins­cli.jar ­i ~/id_rsa ­s http://localhost:8080 list­jobs java.io.EOFException at java.io.DataInputStream.readBoolean(DataInputStream.java:244) Security Enabled Security Disabled
  8. Setting the API token void user_update() { // or create

    … def api_token_plain = conf['api_token_plain'] if (api_token_plain) { assert api_token_plain instanceof String def token_param = new ApiTokenProperty() token_param.@apiToken = new Secret(api_token_plain) user.addProperty(token_param) } … $ puppet resource –modulepath=/ jenkins_user admin jenkins_user { 'admin': ensure => 'present', api_token_plain => '492b8b1a8a5015b5ff597ee8d4c00a43', api_token_public => 'c7311b827cd5244b603b01f96f4c6e24', full_name => 'jenkins admin', public_keys => ['ssh­rsa ...'], } files/puppet_helper.groovy
  9. Pros & cons of service mediated configuration • Pros –

    Don't have to parse/comprehend configuration format – Clever dynamic actions are possible • Cons – Service has to be up – Authentication may need to be working – Performance – API may be complex – API may be in an unknown language
  10. How might this be better? • Out of band administrative

    mechanism; E.g. domain socket • Non-daemon mode for configuration manipulation
  11. Goals • Extend existing puppet­jenkins module • Common configuration between

    resources • Resolve CLI – manifest authentication bootstrapping • Configure the github­oauth plugin • Manage user API tokens so that github­oauth does not cut off swarm slaves
  12. Common resource configuration • Could not identify any examples –

    puppetlabs­aws relies on aws­sdk to pull credentials from the environment • DSL has resource defaults – Scope issues :( • Providers are limited to facts and introspecting on the catalog
  13. PuppetX::Jenkins::Config # manifest.pp class jenkins::cli::config( $cli_jar = undef, $port =

    undef, $ssh_private_key = undef, $puppet_helper = undef, $cli_tries = undef, $cli_try_sleep = undef, ) {} class { 'jenkins::cli::config': ssh_private_key => '/tmp/id_rsa', } # provider/jenkins_foo/cli.rb config = PuppetX::Jenkins::Config.new(catalog) ssh_private_key = config[:ssh_private_key]
  14. Accessing the Catalog • Accessing the catalog from a provider

    not attached to a resource is difficult – Adrien/Hunter axis-of-evil-puppet-constructs pointed out that ::prefetch is called with a list of resources – resources are linked to the catalog
  15. class PuppetX::Jenkins::Provider::Cli < Puppet::Provider def self.prefetch(resources) catalog = resources.first[1].catalog instances(catalog).each

    do |prov| if resource = resources[prov.name] resource.provider = prov end end end end Puppet::Type.type(:jenkins_user).provide(:cli, :parent => PuppetX::Jenkins::Provider::Cli) do def self.instances(catalog = nil) all = user_info_all(catalog) all.collect {|info| from_hash(info)} end end
  16. Resource face export FACTER_jenkins_puppet_helper=/jenkins/files/puppet_helper.groovy export FACTER_jenkins_cli_tries=2 export FACTER_jenkins_ssh_private_key=/id_rsa $ puppet

    resource ­­modulepath=/ jenkins_user ­­debug –trace Debug: Runtime environment: puppet_version=3.8.2, ruby_version=2.0.0, ru … jenkins_user { 'admin': ensure => 'present', api_token_plain => '6107391bb75109e6baf78762399da35d', api_token_public => '62725bc3ffcfdf99a86bc7b9982f7957', full_name => 'jenkins admin', public_keys => ['ssh­rsa …'], } • Extremely useful for debugging • No manifest is parsed; facts to pass data
  17. provider inheritance • Provider confine(s) inherit in a broken state

    • ::commands is ultimately a ::confine • This is kludged around in the core providers Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Provider::Package do commands :rpm => "rpm" end Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do commands :yum => "yum", :rpm => "rpm" end
  18. class PuppetX::Jenkins::Provider::Cli < Puppet::Provider # push a shallow copy of

    the class confines into the subclass # this includes the confine(s) needed for commands # # The subclass seems to function with an empty @commands and any # value we try to push in will get reselt by ::initvars. def self.inherited(subclass) subclass.instance_variable_set(:@confine_collection, @confine_collection.dup) end commands :java => 'java' confine :feature => :retries end
  19. Shared State class PuppetX::Jenkins::Provider::Cli < Puppet::Provider def self.execute_with_auth(cli_cmd, auth_cmd, options

    = {}) ... # we already know that auth is required if class_variable_get(:@@cli_auth_required) return execute_exceptionify(auth_cmd, options) end begin # try first with no auth return execute_exceptionify(cli_cmd, options) rescue AuthError # retry with auth result = execute_exceptionify(auth_cmd, options) class_variable_set(:@@cli_auth_required, true) return result end end end `
  20. Resource ordering • All types need service resource and ssh

    & puppet_helper to be converged • Implemented with a simple wrapper around Puppet::Type::newtype() module PuppetX::Jenkins::Type::Cli def self.newtype(*args, &block) type = Puppet::Type.newtype(*args, &block) type.autorequire(:service) do ['jenkins'] end ... end end PuppetX::Jenkins::Type::Cli.newtype(:jenkins_job) do @doc = "Manage Jenkins' jobs" … end
  21. Putting it all together • Ab-use the params of a

    class as a data source – Use ::prefetch to access the catalog and implement ::instances with a catalog argument – Fall back on facts to support puppet resource • Provider base class – “fixes” inheritance of confine – Shared state via class variables • Type factory wrapper for common autorequire
  22. Status of TPification • Merged but not shipped – Release

    coming “real soon” • Need real-world usage feedback • Still needs to be integrated with existing defined types – Some semantics have changed (fixed) • Need acceptance tests that cover interaction with puppet_helper.groovy
  23. Pain points • No formal mechanism for passing data in

    catalog • No formal means of sharing information between providers • Accessing the catalog can be difficult from a provider • Singleton resources – DSL has classes – No core mechanism for types • Attempting to limit type/provider instances causes fallout • Provider inheritance is funky • Understanding ::prefetch, ::flush, ::instances, etc. requires reading the source – Beware newtype meta-dragons • Types can not be made private to a module • Types are not namespaces - jenkins_credentials instead of jenkins::credentials – Prevents direct conversion between DSL and native types
  24. Links • github.com/jenkinsci/puppet-jenkins - bit.ly/puppet-jenkins • NATIVE_TYPES_AND_PROVIDERS.md - bit.ly/tp-readme •

    github.com/jenkinsci/github-oauth-plugin - bit.ly/github-oauth-plugin • freenode: ^conner • Twitter: @jhoblitt • joshua.hoblitt.com/ Contact