51
WRITING & SHARING GREAT MODULES Adrien Thebo, Puppet Labs Twitter: @nullfinch | Freenode: finch

Writing & Sharing Great Modules on the Puppet Forge

Embed Size (px)

Citation preview

WRITING & SHARINGGREAT MODULES

Adrien Thebo, Puppet LabsTwitter: @nullf inch | Freenode: f inch

WHO IS THIS GUY?On/Off Ops/Dev, ~8 yearsOperations Engineer @puppetlabs, 2 yearsCommunity Developer @puppetlabs, 3 months

THINGS I DOpuppet-network: cross-platform network configurationpuppet-portage: Puppet ♥ Gentoor10k: smarter Puppet deployment, powered by robotsvagrant-hosts: it’s always a DNS problemvagrant-pe_build: From zero to PE in vagrant up

OTHER THINGS I DOTalk too fastIf I become completely unintelligibleslow me down

LET’S TALK ABOUTMODULES

BEST PRACTICES‽Traditional development: 40+ years to matureModern config. mgmt: 15 years, maxBest practices haven’t yet been established

SO WHERE DO WE START?Separate your logic and configurationKnow your interfaceUse semantic versioningReuse everythingUse the community

DATA/LOGICSEPARATION

SEPARATE LOGIC FROM DATALogic != Data

Example: configure a service on different platformsShouldn’t have to update every file in a module

PACKAGE/FILE/SERVICEHumble beginnings for many modules

class mysql::server { package { 'mysql-server': ensure => present, }

file { '/etc/mysql/my.cnf': ensure => present, content => template('mysql/server/my.cnf.erb'), require => Package['mysql-server'], }

service { 'mysqld': ensure => running, enable => true, subscribe => File['/etc/mysql/my.conf'], }}

PROBLEMS WITH PACKAGE/FILE/SERVICENothing inherently wrongOverly simpleVery staticGenerally requires overhaul for different platforms

RUDIMENTARY DATA/LOGIC SEPARATIONclass mysql::server { include mysql::params

package { 'mysql-server': name => $mysql::params::server_package, ensure => present, }

file { 'my.cnf': path => $mysql::params::server_config, ensure => present, source => 'puppet:///modules/nrpe/nrpe.cfg', require => Package['nagios-nrpe-server'], }

service { 'mysql-server': name => $mysql::params::server_service, ensure => running, enable => true, subscribe => File['my.cnf'], }}

HARDCODING TUNABLE VALUESWant to prevent people from reusing your modules?Hardcode everything!

USING PARAMS, BAD:

Params class = goodWhy is this bad?

Site specific defaults?INSECURE DEFAULTS‽

class mysql::params { $allow_hosts = '0.0.0.0/0' $root_user = 'root' # ̄\_(ツ)_/̄ $root_password = 'changeme'}

USING PARAMS, GOOD:

Force user to supply dataFail fast

class mysql::params( $allow_hosts, # Force the module user to fill this out $root_password, # Fail fast rather than potentially use bad data $root_user = 'root' # Sane default) {}

DATA BINDING

DATA BINDINGNew in Puppet 3: data bindingProvides a method for configuring modules

USING DATA BINDINGDefine data in a data store

filedatabaseForeman

Automatically load data in the relevant manifests

USING DATA BINDINGclass mysql::params( $allow_hosts, $database_password, $database_user = 'root') {}

# $datadir/common.yaml---mysql::params::allow_hosts: '10.126.8.0/24'

# $datadir/qa.mysite.local.yaml---mysql::params::allow_hosts: '10.134.8.0/24'

USING MODULES ASINTERFACES

MODULES AS INTERFACESPuppet simplifies management of servicesDefines how people interact with that servicePuppet modules define an interface for that service

Creates two challengesWhat options are supported?What options should users configure?

BE OPINIONATEDCannot make every option tunableYou’ll go insane

Require mandatory dataAdd parameters for frequently changed dataOffer an ‘override’ option

BUT OTHER OPINIONS ARE NICE TOOYou can’t always support every optionAllow people to directly insert their own configuration

OVERRIDE EXAMPLE: PARTIAL TEMPLATESModule provides template fragmentsUser assembles these into a full config

CREATING A PARTIAL TEMPLATE<%# nginx/templates/vhost/_listen.conf.erb %><%# Configuration fragment for listening on IPv4 and IPv6 with SSL %><% unless @sslonly -%> listen <%= port %>;<% if scope.lookupvar('::ipaddress6') -%> listen [::]:<%= port %>;<% end -%><% end -%>

<% if ssl -%> listen <%= ssl_port %> ssl;<% if scope.lookupvar('::ipaddress6') -%> listen [::]:<%= ssl_port %> ssl;<% end -%><% end -%>

USING PARTIAL TEMPLATESExample: my_nginx_app/templates/nginx-

vhost.conf.erbserver {<%= scope.function_template(['nginx/vhost/_listen.conf.erb']) %>

root /usr/share/empty;

location / { proxy_pass <%= @proto %>://workers; proxy_redirect off; proxy_next_upstream error timeout invalid_header http_500 http_503; proxy_connect_timeout 5; }}

SEMVER

WITHOUT SEMANTIC VERSIONINGA cautionary tale of versioning gone bad

1.0.0 Initial release for managing cacti1.1.1 Change server param to servername1.1.2 Move params from cacti::data to cacti::params1.2.0 Updated README1.2.1 Drops support for CentOS 51.3.0 This module now manages munin2.0.0 I can update versions whenever I want?10.51.100 THIS IS AWESOME!-4.number.999999999999 I’VE CREATED A MONSTER

UPGRADING SHOULD BE BORINGAPI breaks mean upgrading is dangerousNobody wants to upgrade if it means explosionsSemantic versioning helps mitigate this

WHAT IS SEMVER?Version strings should have meaningReleases match the format x.y.zValues indicate what’s changed in that version

MAJOR RELEASESExample: x.0.0

Backwards incompatible changes

Changing class namesChanging parameter namesDropping platform support

MINOR RELEASESExample: x.y.0

Backwards compatible features

Adding support for new platformsAdding parametersAdding features

PATCH RELEASESExample: x.y.z

BugfixesDocumentationTestsAnything that can’t be called a feature

SEMVER AS A CONTRACTIf you use SemVer, you’re making an agreement to avoidmaking breaking changesWhat is a breaking change?What’s public?What’s private?

WHAT IS PUBLIC?Publicly exposed classesClass parametersThe final behavior of your class

WHAT IS PRIVATE?The actual resources used in your classes and defines

Resources themselves are implementation, notClasses that are documented as private

If you document that a class is private, people shouldn’tuse it

SAFETY IN SEMVERSemVer takes the risk out of upgradingYou can understand the implications of upgrading rightawayHow Puppet does it

3.1.0: Better support for Ruby code loading3.1.1: Security fixes3.2.0: External CA support, types & providers forOpenWRT4.0.0: Tachyon based transport layer

Not really.

MAKE OTHER PEOPLE DOYOUR WORK

AKA

REUSE MODULES

REUSE MODULESWriting good code is hard.Make other people do your work.

DISCOVERY VIA THE FORGEPuppet Forge has 1000+ modulesProvides a single point to discover and install modulesEasy access to documentation

READMECHANGELOGType & provider documentation

GET DEPENDENCIES FROM THE FORGEgrey% puppet module search postgresNotice: Searching https://forge.puppetlabs.com ...NAME DESCRIPTIONknowshan-phppgadmin Install and configure phpPgAdminDropPod-postgres A basic type for managing Postgrescamptocamp-pgconf Manage postgresql.conf entriesinkling-postgresql PostgreSQL defined resource typesakumria-postgresql Install and configure the Postgresqlpuppetlabs-postgresql PostgreSQL defined resource types

COLLABORATE ON EXISTING MODULESLots of good modules are out thereEncourage people to publish on the ForgeHelp improve existing modulesOnl you can prevent ecosystem fragmentation

SMALL CONTRIBUTIONS HELPDocumentationBug fixesIssue reports

ESTABLISH ACOMMUNITY

SURVIVING SUCCESSYour module is a hit!Prepare for a deluge of bug reports and feature requests

POPULARITY = MORE WORKThings users are good at:

Finding bugsFiling feature requestsRequesting things like “documentation”Finding more bugs

Funny how these match how you can help othercontributors

HARNESS YOUR USERSBug reports = people careShow people how to helpAsk for pull requestsGuide people through the contribution process

ENDQUESTIONS?

COMMENTS?SNARKY REMARKS?

AWKWARD SILENCE?

CREDITSPRESENTATION DONE WITH REVEAL.JS