Class: Pupistry::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/pupistry/agent.rb

Overview

Functions for running the Pupistry agent aka “apply mode” to actually download and run Puppet against the contents of the artifact.

Class Method Summary collapse

Class Method Details

.apply(options) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/pupistry/agent.rb', line 56

def self.apply(options)
  ## Download and apply the latest artifact (if any)

  # Fetch artifact versions
  $logger.info 'Checking version of artifact available...'

  artifact = Pupistry::Artifact.new
  artifact.checksum = artifact.fetch_latest

  unless artifact.checksum
    $logger.error 'There is no current artifact available for download, no steps can be taken.'
    return false
  end

  artifact_installed = Pupistry::Artifact.new
  artifact_installed.checksum = artifact_installed.fetch_installed

  if artifact_installed.checksum
    $logger.debug "Currently on #{artifact_installed.checksum}"
  else
    $logger.debug 'No currently installed artifact - blank slate!'
  end

  # Download the new artifact if one has changed. If we already have this
  # version, then we should skip downloading and go straight to running
  # Puppet - unless the user runs with --force (eg to fix a corrupted
  # artifact).

  if artifact.checksum != artifact_installed.checksum || options[:force]
    $logger.warn 'Forcing download of latest artifact regardless of current one.' if options[:force]

    # Install the artifact
    $logger.info "Downloading latest artifact (#{artifact.checksum})..."

    artifact.fetch_artifact
    artifact.unpack
    artifact.hieracrypt_decrypt

    unless artifact.install
      $logger.fatal 'An unexpected error happened when installing the latest artifact, cancelling Puppet run'
      return false
    end

    # Remove temporary unpacked files
    artifact.clean_unpack
  else
    $logger.info 'Already have latest artifact applied.'

    # By default we run Puppet even if we have the latest artifact. There's
    # some grounds for debate about whether this is the right thing - in some
    # ways it is often a waste of CPU, since if the artifact hasn't changed,
    # then it's unlikley anything else has changed.
    #
    # But that's not always 100% true - Puppet will undo local changes or
    # upgrade package versions (ensure => latest) if appropiate, so we should
    # act like the standard command and attempt to apply whatever we can.
    #
    # To provide users with options, we provide the --lazy parameter to avoid
    # running Puppet except when the artifact changes. By default, Puppet
    # runs every thing to avoid surprise.

    if options[:minimal]
      $logger.info 'Running with minimal effort mode enabled, not running Puppet since artifact version already applied'
      return false
    end

  end

  # If the environment has been specified, use it.
  environment = $config['agent']['environment'] || 'master'
  # override if environment is supplied on CLI
  environment = options["environment"] || environment

  unless Dir.exist?("#{$config['agent']['puppetcode']}/#{environment}")
    $logger.fatal "The requested branch/environment of #{environment} does not exist, unable to run Puppet"
    return false
  end

  # Execute Puppet.
  puppet_cmd = 'puppet apply'

  puppet_cmd += ' --noop' if options[:noop]
  puppet_cmd += ' --show_diff' if options[:verbose]

  puppet_cmd += " --environment #{environment}"
  puppet_cmd += " --confdir #{$config['agent']['puppetcode']}"
  puppet_cmd += " --environmentpath #{$config['agent']['puppetcode']}"
  puppet_cmd += " --modulepath #{build_modulepath(environment)}"
  puppet_cmd += " --hiera_config #{$config['agent']['puppetcode']}/#{environment}/hiera.yaml"
  puppet_cmd += " #{$config['agent']['puppetcode']}/#{environment}/manifests/"

  $logger.info 'Executing Puppet...'
  $logger.debug "With: #{puppet_cmd}"

  $logger.error 'An unexpected issue occured when running puppet' unless system puppet_cmd
end

.build_modulepath(environment) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/pupistry/agent.rb', line 153

def self.build_modulepath(environment)

  environment_path = "#{$config['agent']['puppetcode']}/#{environment}"
  environment_conf = "#{environment_path}/environment.conf"

  configured_paths = []

  if File.exist?(environment_conf)
    $logger.debug "Adding modulepath config from '#{environment_path}'"

    File.open(environment_conf, 'r').readlines.each do |line|
      if line !~ /^\s*#/ && /^(.*)=(.*)/ =~ line
        key, val = $1.strip, $2.strip
        configured_paths = val.split(':') if key == 'modulepath'
      end
    end
  end

  modulepaths = configured_paths.map { |path| File.expand_path(path, environment_path) }

  # Ensure '<environment_path>/modules' in modulepath.
  ensure_path = File.expand_path('modules', environment_path)
  modulepaths.insert(0, ensure_path) unless modulepaths.include? ensure_path

  modulepaths.join(File::PATH_SEPARATOR)
end

.daemon(options) ⇒ Object

Run as a daemon



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/pupistry/agent.rb', line 12

def self.daemon(options)
  # Since options comes from Thor, it can't be modified, so we need to
  # copy the options and then we can edit it.

  options_new = options.inject({}) do |new, (name, value)|
    new[name] = value
    new
  end

  # If the minimal mode has been enabled in config, respect.
  options_new[:minimal] = true if $config['agent']['daemon_minimal']

  # If no frequency supplied, use 300 seconds safe default.
  $config['agent']['daemon_frequency'] = 300 unless $config['agent']['daemon_frequency']

  # Use rufus-scheduler to run our apply job as a regularly scheduled job
  # but with build in locking handling.

  $logger.info "Launching daemon... frequency of #{$config['agent']['daemon_frequency']} seconds."

  begin

    scheduler = Rufus::Scheduler.new

    scheduler.every "#{$config['agent']['daemon_frequency']}s", overlap: false, timeout: '1d', first_at: Time.now + 1 do
      $logger.info "Triggering another Pupistry run (#{$config['agent']['daemon_frequency']}s)"
      apply options_new
    end

    scheduler.join

  rescue Rufus::Scheduler::TimeoutError
    $logger.error 'A run of Pupistry timed out after 1 day as a safety measure. There may be a bug or a Puppet action causing it to get stuck'

  rescue SignalException
    # Clean shutdown signal (eg SIGTERM)
    $logger.info 'Clean shutdown of Pupistry daemon requests'
    exit 0

  rescue StandardError => e
    raise e
  end
end