Ryan Lane has uploaded a new change for review.
https://gerrit.wikimedia.org/r/74108
Change subject: Use grains for deployment targets
......................................................................
Use grains for deployment targets
Rather than using minion regex for targeting deployments, this
change uses grains for targeting. It also includes a refactor of
the git-deploy hooks.
Change-Id: I65bb6f104d997c2635af848daeb34f10b7e95988
---
M manifests/role/deployment.pp
M modules/deployment/files/git-deploy/hooks/depends.py
M modules/deployment/files/git-deploy/hooks/deploylib.py
M modules/deployment/files/git-deploy/hooks/shared.py
A modules/deployment/files/runners/deploy.py
M modules/deployment/manifests/salt_master.pp
A modules/deployment/templates/deploy_runner.conf.erb
M modules/deployment/templates/pillars/deploy.sls.erb
D modules/deployment/templates/runners/deploy.py.erb
9 files changed, 205 insertions(+), 201 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/operations/puppet
refs/changes/08/74108/1
diff --git a/manifests/role/deployment.pp b/manifests/role/deployment.pp
index 243dc0a..2b382a7 100644
--- a/manifests/role/deployment.pp
+++ b/manifests/role/deployment.pp
@@ -33,6 +33,23 @@
"fluoride/fluoride" => "http://${deploy_server_eqiad}/fluoride/fluoride",
},
}
+ # deployment_target grain value for this repo. This must match the
deployment::target
+ # value that is being set on the targets via puppet. If unset, the default
value
+ # is the repo name
+ $deployment_repo_grains = {
+ "common" => "mediawiki",
+ "private" => "mediawiki",
+ "slot0" => "mediawiki",
+ "slot1" => "mediawiki",
+ "beta0" => "mediawiki",
+ "l10n-slot0" => "mediawiki",
+ "l10n-slot1" => "mediawiki",
+ "l10n-beta0" => "mediawiki",
+ "parsoid/Parsoid" => "parsoid",
+ "parsoid/config" => "parsoid",
+ "eventlogging/EventLogging" => "eventlogging",
+ "fluoride/fluoride" => "eventlogging",
+ }
# Sed the .gitmodules file for the repo according to the following rules
# TODO: rename this to something more specific
$deployment_repo_regex = {
@@ -113,10 +130,6 @@
}
class role::deployment::salt_masters::production {
- $mediawiki_regex =
"^(srv|mw|snapshot|tmh)|(searchidx2|searchidx1001).*.(eqiad|pmtpa).wmnet$|^(hume|fenari).wikimedia.org$"
- $parsoid_regex = "^(wtp10[01][0-9]|wtp102[0-4])\..*"
- $eventlogging_regex = "^(vanadium).eqiad.wmnet$"
- $fluoride_regex = "^(vanadium).eqiad.wmnet$"
$deployment_servers = {
"pmtpa" => "tin.eqiad.wmnet",
"eqiad" => "tin.eqiad.wmnet",
@@ -132,20 +145,7 @@
deployment_repo_checkout_submodules =>
$role::deployment::salt_masters::common::deployment_repo_checkout_submodules,
deployment_repo_locations =>
$role::deployment::salt_masters::common::deployment_repo_locations,
deployment_repo_dependencies =>
$role::deployment::salt_masters::common::deployment_repo_dependencies,
- deployment_minion_regex => {
- "private" => $mediawiki_regex,
- "common" => $mediawiki_regex,
- "slot0" => $mediawiki_regex,
- "slot1" => $mediawiki_regex,
- "beta0" => '^$', # no master branch in production
- "l10n-slot0" => $mediawiki_regex,
- "l10n-slot1" => $mediawiki_regex,
- "l10n-beta0" => '^$', # no master branch in production
- "parsoid/Parsoid" => $parsoid_regex,
- "parsoid/config" => $parsoid_regex,
- "eventlogging/EventLogging" => $eventlogging_regex,
- "fluoride/fluoride" => $fluoride_regex,
- },
+ deployment_repo_grains =>
$role::deployment::salt_masters::common::deployment_repo_grains,
deployment_deploy_redis => {
"host" => "tin.eqiad.wmnet",
"port" => 6379,
@@ -155,10 +155,6 @@
}
class role::deployment::salt_masters::labs {
- $mediawiki_regex =
"^(i-000004ff|i-000004cc|i-0000031b|i-0000031a).pmtpa.wmflabs"
- $parsoid_regex = "^$"
- $eventlogging_regex = "^$"
- $fluoride_regex = "^$"
$deployment_servers = {
"pmtpa" => "i-00000390.pmtpa.wmflabs",
# no eqiad zone, yet
@@ -175,20 +171,7 @@
deployment_repo_checkout_submodules =>
$role::deployment::salt_masters::common::deployment_repo_checkout_submodules,
deployment_repo_locations =>
$role::deployment::salt_masters::common::deployment_repo_locations,
deployment_repo_dependencies =>
$role::deployment::salt_masters::common::deployment_repo_dependencies,
- deployment_minion_regex => {
- "private" => $mediawiki_regex,
- "common" => $mediawiki_regex,
- "slot0" => $mediawiki_regex,
- "slot1" => $mediawiki_regex,
- "beta0" => $mediawiki_regex,
- "l10n-slot0" => $mediawiki_regex,
- "l10n-slot1" => $mediawiki_regex,
- "l10n-beta0" => $mediawiki_regex,
- "parsoid/Parsoid" => $parsoid_regex,
- "parsoid/config" => $parsoid_regex,
- "eventlogging/EventLogging" => $eventlogging_regex,
- "fluoride/fluoride" => $fluoride_regex,
- },
+ deployment_repo_grains =>
$role::deployment::salt_masters::common::deployment_repo_grains,
deployment_deploy_redis => {
"host" => "i-00000390.pmtpa.wmflabs",
"port" => 6379,
diff --git a/modules/deployment/files/git-deploy/hooks/depends.py
b/modules/deployment/files/git-deploy/hooks/depends.py
index 973e5b7..aecd43a 100644
--- a/modules/deployment/files/git-deploy/hooks/depends.py
+++ b/modules/deployment/files/git-deploy/hooks/depends.py
@@ -11,14 +11,15 @@
#TODO: Use this message to notify IRC
#msg = os.environ['DEPLOY_DEPLOY_TEXT']
- deploylib.update_repos(prefix, tag)
+ prefixlib = deploylib.DeployLib(prefix)
+ prefixlib.update_repos(tag)
# In general, for dependent repos, the parent repo is handling
# fetch and checkout. Some dependent repos also update outside
# of their parent repos. If the repo forces a sync, then we should
# handle it.
if force:
- deploylib.fetch(prefix)
- deploylib.checkout(prefix, "True")
+ prefixlib.fetch()
+ prefixlib.checkout("True")
if __name__ == "__main__":
main()
diff --git a/modules/deployment/files/git-deploy/hooks/deploylib.py
b/modules/deployment/files/git-deploy/hooks/deploylib.py
index 7d64fd7..2b7dbc9 100755
--- a/modules/deployment/files/git-deploy/hooks/deploylib.py
+++ b/modules/deployment/files/git-deploy/hooks/deploylib.py
@@ -5,58 +5,76 @@
import json
-def update_repos(prefix, tag):
- print "Running: sudo salt-call -l quiet --out json pillar.data"
- p = subprocess.Popen("sudo salt-call -l quiet --out json pillar.data",
- shell=True, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- out = p.communicate()[0]
- try:
- pillar = json.loads(out)
- except ValueError:
- print ("JSON data wasn't loaded from the pillar call. "
- "git-deploy can't configure itself. Exiting.")
- return 1
- try:
- repodir = pillar['repo_locations'][prefix]
- except KeyError:
- print ("This repo isn't configured. "
- "Have you added it in puppet? Exiting.")
- return 1
- try:
- checkout_submodules = pillar['repo_checkout_submodules'][prefix]
- except KeyError:
- checkout_submodules = "False"
+class DeployLib(object):
- # Ensure the fetch will work for the repo
- p = subprocess.Popen('git update-server-info',
- cwd=repodir + '/.git/', shell=True,
- stderr=subprocess.PIPE)
- err = p.communicate()[0]
- if err:
- print err
- # Ensure the fetch will work for the submodules
- if checkout_submodules:
- p = subprocess.Popen('git submodule foreach "git tag %s"' % tag,
- cwd=repodir, shell=True,
- stdout=subprocess.PIPE,
+ __config = {}
+
+ def __init__(self, prefix):
+ self.__fetch_config(prefix)
+
+ def __fetch_config(self, prefix):
+ print "Running: sudo salt-call -l quiet --out json pillar.data"
+ p = subprocess.Popen("sudo salt-call -l quiet --out json pillar.data",
+ shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out = p.communicate()[0]
- p = subprocess.Popen('git submodule foreach '
- '"submodule-update-server-info"',
- cwd=repodir, shell=True,
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out = p.communicate()[0]
+ try:
+ pillar = json.loads(out)
+ options = {'repo_locations': None
+ 'repo_checkout_submodules': False,
+ 'repo_dependencies': []}
+ for option, default in options.items():
+ try:
+ __config[option] = pillar[option][prefix]
+ except KeyError:
+ if default:
+ __config[option] = default
+ else:
+ print (option + " isn't configured. "
+ "Have you added it in puppet? Exiting.")
+ return False
+ __config['prefix'] = prefix
+ return True
+ except ValueError:
+ print ("JSON data wasn't loaded from the pillar call. "
+ "git-deploy can't configure itself. Exiting.")
+ return False
- # Ensure repos we depend on are handled
- if prefix in pillar['repo_dependencies']:
- dependencies = pillar['repo_dependencies'][prefix]
+ def get_config(self):
+ return self.__config
+
+ def update_repos(self, tag):
+ repodir = __config['repo_locations']
+ checkout_submodules = __config['repo_checkout_submodules']
+
+ # Ensure the fetch will work for the repo
+ p = subprocess.Popen('git update-server-info',
+ cwd=repodir + '/.git/', shell=True,
+ stderr=subprocess.PIPE)
+ err = p.communicate()[0]
+ if err:
+ print err
+ # Ensure the fetch will work for the submodules
+ if checkout_submodules:
+ p = subprocess.Popen('git submodule foreach "git tag %s"' % tag,
+ cwd=repodir, shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+ p = subprocess.Popen('git submodule foreach '
+ '"submodule-update-server-info"',
+ cwd=repodir, shell=True,
+ stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+
+ # Ensure repos we depend on are handled
+ dependencies = __config['repo_dependencies']
for dependency in dependencies:
dependency_script = ('/var/lib/git-deploy/dependencies/%s.dep' %
(dependency))
if os.path.exists(dependency_script):
dependency_script = (dependency_script + " %s %s" %
- (dependency, prefix))
+ (dependency, __config['prefix']))
p = subprocess.Popen(dependency_script, shell=True,
stderr=subprocess.PIPE)
out = p.communicate()[0]
@@ -67,52 +85,54 @@
dependency_script)
return 1
+ def fetch(self):
+ prefix = __config['prefix']
+ print ("Running: sudo salt-call -l quiet publish.runner "
+ "deploy.fetch '%s'" % (prefix))
+ p = subprocess.Popen("sudo salt-call -l quiet publish.runner "
+ "deploy.fetch '%s'" % (prefix),
+ shell=True,
+ stdout=subprocess.PIPE)
+ out = p.communicate()[0]
-def fetch(prefix):
- print ("Running: sudo salt-call -l quiet publish.runner "
- "deploy.fetch '%s'" % (prefix))
- p = subprocess.Popen("sudo salt-call -l quiet publish.runner "
- "deploy.fetch '%s'" % (prefix), shell=True,
- stdout=subprocess.PIPE)
- out = p.communicate()[0]
+ def checkout(self, force):
+ prefix = __config['prefix']
+ print ("Running: sudo salt-call -l quiet publish.runner "
+ "deploy.checkout '%s,%s'" % (prefix, force))
+ p = subprocess.Popen("sudo salt-call -l quiet publish.runner "
+ "deploy.checkout '%s,%s'" % (prefix, force),
+ shell=True, stdout=subprocess.PIPE)
+ out = p.communicate()[0]
-
-def checkout(prefix, force):
- print ("Running: sudo salt-call -l quiet publish.runner "
- "deploy.checkout '%s,%s'" % (prefix, force))
- p = subprocess.Popen("sudo salt-call -l quiet publish.runner "
- "deploy.checkout '%s,%s'" % (prefix, force),
- shell=True, stdout=subprocess.PIPE)
- out = p.communicate()[0]
-
-
-def ask(prefix, stage, force=False):
- if stage == "fetch":
- check = "/usr/local/bin/deploy-info --repo=%s --fetch"
- elif stage == "checkout":
- check = "/usr/local/bin/deploy-info --repo=%s"
- p = subprocess.Popen(check % (prefix), shell=True, stdout=subprocess.PIPE)
- out = p.communicate()[0]
- print out
- while True:
- answer = raw_input("Continue? ([d]etailed/[C]oncise report,"
- "[y]es,[n]o,[r]etry): ")
- if not answer or answer == "c" or answer == "C":
- p = subprocess.Popen(check % (prefix), shell=True,
- stdout=subprocess.PIPE)
- out = p.communicate()[0]
- print out
- elif answer == "d" or answer == "D":
- p = subprocess.Popen(check % (prefix) + " --detailed",
- shell=True, stdout=subprocess.PIPE)
- out = p.communicate()[0]
- print out
- elif answer == "Y" or answer == "y":
- return True
- elif answer == "N" or answer == "n":
- return False
- elif answer == "R" or answer == "r":
- if stage == "fetch":
- fetch(prefix)
- if stage == "checkout":
- checkout(prefix, force)
+ def ask(self, stage, force=False):
+ prefix = __config['prefix']
+ if stage == "fetch":
+ check = "/usr/local/bin/deploy-info --repo=%s --fetch"
+ elif stage == "checkout":
+ check = "/usr/local/bin/deploy-info --repo=%s"
+ p = subprocess.Popen(check % (prefix), shell=True,
+ stdout=subprocess.PIPE)
+ out = p.communicate()[0]
+ print out
+ while True:
+ answer = raw_input("Continue? ([d]etailed/[C]oncise report,"
+ "[y]es,[n]o,[r]etry): ")
+ if not answer or answer == "c" or answer == "C":
+ p = subprocess.Popen(check % (prefix), shell=True,
+ stdout=subprocess.PIPE)
+ out = p.communicate()[0]
+ print out
+ elif answer == "d" or answer == "D":
+ p = subprocess.Popen(check % (prefix) + " --detailed",
+ shell=True, stdout=subprocess.PIPE)
+ out = p.communicate()[0]
+ print out
+ elif answer == "Y" or answer == "y":
+ return True
+ elif answer == "N" or answer == "n":
+ return False
+ elif answer == "R" or answer == "r":
+ if stage == "fetch":
+ fetch()
+ if stage == "checkout":
+ checkout(force)
diff --git a/modules/deployment/files/git-deploy/hooks/shared.py
b/modules/deployment/files/git-deploy/hooks/shared.py
index 81a9ab7..798b1d4 100644
--- a/modules/deployment/files/git-deploy/hooks/shared.py
+++ b/modules/deployment/files/git-deploy/hooks/shared.py
@@ -20,12 +20,15 @@
serv.rpush("deploy:log", "!log {0} started synchronizing "
"{1} '{2}'".format(getpass.getuser(), tag, log))
- deploylib.update_repos(prefix, tag)
- deploylib.fetch(prefix)
- if not deploylib.ask(prefix, 'fetch'):
+ prefixlib = deploylib.DeployLib(prefix)
+ if not prefixlib.get_config():
return 1
- deploylib.checkout(prefix, force)
- if not deploylib.ask(prefix, 'checkout', force):
+ prefixlib.update_repos(tag)
+ prefixlib.fetch()
+ if not prefixlib.ask('fetch'):
+ return 1
+ prefixlib.checkout(force)
+ if not prefixlib.ask('checkout', force):
return 1
serv.rpush("deploy:log", "!log {0} synchronized "
diff --git a/modules/deployment/files/runners/deploy.py
b/modules/deployment/files/runners/deploy.py
new file mode 100755
index 0000000..2a708f7
--- /dev/null
+++ b/modules/deployment/files/runners/deploy.py
@@ -0,0 +1,49 @@
+'''
+Authn wrapper for deployment peer calls
+'''
+
+import salt.key
+import salt.client
+import re
+import yaml
+
+
+def __get_conf(repo, key):
+ try:
+ config = yaml.load(file('/etc/salt/deploy_runner.conf', 'r'))
+ return config[key][repo]
+ except IOError:
+ return ''
+ except YAMLError:
+ return ''
+ except KeyError:
+ return ''
+
+
+def fetch(repo):
+ '''
+ Fetch from a master, for the specified repo
+ '''
+ grain = __get_conf(repo, 'deployment_repo_grains')
+ if not grain:
+ return "No grain defined for this repo."
+ client = salt.client.LocalClient(__opts__['conf_file'])
+ cmd = 'deploy.fetch'
+ # comma in the tuple is a workaround for a bug in salt
+ arg = (repo,)
+ client.cmd(grain, cmd, expr_form='grain', arg=arg, timeout=30,
+ ret='deploy_redis')
+
+
+def checkout(repo, grain, reset=False):
+ '''
+ Checkout from a master, for the specified repo
+ '''
+ grain = __get_conf(repo, 'deployment_repo_grains')
+ if not grain:
+ return "No grain defined for this repo."
+ client = salt.client.LocalClient(__opts__['conf_file'])
+ cmd = 'deploy.checkout'
+ arg = (repo, reset)
+ client.cmd(grain, cmd, expr_form='grain', arg=arg, timeout=30,
+ ret='deploy_redis')
diff --git a/modules/deployment/manifests/salt_master.pp
b/modules/deployment/manifests/salt_master.pp
index 40baf4a..b5802cc 100644
--- a/modules/deployment/manifests/salt_master.pp
+++ b/modules/deployment/manifests/salt_master.pp
@@ -1,5 +1,11 @@
-class deployment::salt_master($state_dir="/srv/salt",
$runner_dir="/srv/runners", $pillar_dir="/srv/pillars",
$module_dir="/srv/salt/_modules", $returner_dir="/srv/salt/_returners",
$deployment_servers={}, $deployment_minion_regex=".*",
$deployment_repo_urls={}, $deployment_repo_regex={},
$deployment_repo_locations={}, $deployment_repo_checkout_module_calls={},
$deployment_repo_checkout_submodules={}, $deployment_repo_dependencies = {},
$deployment_deploy_redis={}) {
+class deployment::salt_master($state_dir="/srv/salt",
$runner_dir="/srv/runners", $pillar_dir="/srv/pillars",
$module_dir="/srv/salt/_modules", $returner_dir="/srv/salt/_returners",
$deployment_servers={}, $deployment_repo_grains={}, $deployment_repo_urls={},
$deployment_repo_regex={}, $deployment_repo_locations={},
$deployment_repo_checkout_module_calls={},
$deployment_repo_checkout_submodules={}, $deployment_repo_dependencies = {},
$deployment_deploy_redis={}) {
file {
+ "/etc/salt/deploy_runner.conf":
+ content => template("deployment/deploy_runner.conf.erb"),
+ mode => 0444,
+ owner => root,
+ group => root,
+ require => [Package["salt-master"]];
"${state_dir}/deploy":
ensure => directory,
mode => 0555,
@@ -19,7 +25,7 @@
group => root,
require => [File["${state_dir}/deploy"]];
"${runner_dir}/deploy.py":
- content => template("deployment/runners/deploy.py.erb"),
+ source => "puppet:///deployment/runners/deploy.py"),
mode => 0555,
owner => root,
group => root,
@@ -69,6 +75,11 @@
subscribe => [File["${pillar_dir}/deployment/init.sls"],
File["${pillar_dir}"]],
refreshonly => true,
require => [Package["salt-master"]];
+ "refresh_master_pillars":
+ command => "/usr/bin/salt -G 'master_node:True' saltutil.refresh_pillar",
+ subscribe => [File["${pillar_dir}/deployment/init.sls"],
File["${pillar_dir}"]],
+ refreshonly => true,
+ require => [Package["salt-master"]];
"refresh_deployment_modules":
command => "/usr/bin/salt -G 'deployment_target:*'
saltutil.sync_modules",
subscribe =>
[File["${module_dir}/deploy.py"],File["${module_dir}/parsoid.py"]],
diff --git a/modules/deployment/templates/deploy_runner.conf.erb
b/modules/deployment/templates/deploy_runner.conf.erb
new file mode 100644
index 0000000..52722ce
--- /dev/null
+++ b/modules/deployment/templates/deploy_runner.conf.erb
@@ -0,0 +1 @@
+deployment_repo_grains: {<% deployment_repo_grains.sort.each do |repo, grain|
%>'<%= repo %>': '<%= grain %>',<% end %>}
diff --git a/modules/deployment/templates/pillars/deploy.sls.erb
b/modules/deployment/templates/pillars/deploy.sls.erb
index 32d68ef..737e8b1 100644
--- a/modules/deployment/templates/pillars/deploy.sls.erb
+++ b/modules/deployment/templates/pillars/deploy.sls.erb
@@ -1,5 +1,6 @@
repo_locations: {<% deployment_repo_locations.sort.each do |repo, location|
%>'<%= repo %>': '<%= location %>',<% end %>}
repo_minion_regex: {<% deployment_minion_regex.sort.each do |repo, regex|
%>'<%= repo %>': '<%= regex %>',<% end %>}
+repo_grains: {<% deployment_repo_grains.sort.each do |repo, grain| %>'<%= repo
%>': '<%= grain %>',<% end %>}
repo_checkout_submodules: {<% deployment_repo_checkout_submodules.sort.each do
|repo, checkout_submodule| %>'<%= repo %>': '<%= checkout_submodule %>',<% end
%>}
repo_urls: {<% deployment_repo_urls.sort.each do |site, repos| %>'<%= site
%>': {<% repos.sort.each do |repo, url| %>'<%= repo %>': '<%= url %>',<% end
%>},<% end %>}
repo_regex: {<% deployment_repo_regex.sort.each do |repo, regexes| %>'<%= repo
%>': [<% regexes.sort.each do |before, after| %>{'<%= before %>': '<%= after
%>'},<% end %>],<% end %>}
diff --git a/modules/deployment/templates/runners/deploy.py.erb
b/modules/deployment/templates/runners/deploy.py.erb
deleted file mode 100755
index 67705be..0000000
--- a/modules/deployment/templates/runners/deploy.py.erb
+++ /dev/null
@@ -1,65 +0,0 @@
-'''
-Wrapper for salt commands to see which minions failed
-'''
-
-import salt.key
-import salt.client
-import re
-
-# TODO: make this configurable
-reg = {<% deployment_minion_regex.sort.each do |repo,regex| %>'<%= repo %>':
'<%= regex %>',<% end %>}
-
-def fetch(repo):
- '''
- Fetch from a master, for the specified repo
- '''
- client = salt.client.LocalClient(__opts__['conf_file'])
- cmd = 'deploy.fetch'
- if repo not in reg:
- return "Error: repo isn't defined"
- regex = reg[repo]
- arg = (repo,)
- minions = client.cmd(regex, cmd, expr_form='pcre', arg=arg, timeout=30,
ret='deploy_redis')
- key = salt.key.Key(__opts__)
- keys = key.list_keys()
- return report(minions,keys,repo)
-
-def checkout(repo, reset=False):
- '''
- Fetch from a master, for the specified repo
- '''
- client = salt.client.LocalClient(__opts__['conf_file'])
- cmd = 'deploy.checkout'
- if repo not in reg:
- return "Error: repo isn't defined"
- regex = reg[repo]
- arg = (repo,reset)
- minions = {}
- minions = client.cmd(regex, cmd, expr_form='pcre', arg=arg, timeout=30,
ret='deploy_redis')
- key = salt.key.Key(__opts__)
- keys = key.list_keys()
- return report(minions,keys,repo)
-
-def report(minions,keys,repo):
- success = []
- timedout = []
- fail = {}
- for minion,ret in sorted(minions.items()):
- if not isinstance(ret, dict):
- fail[minion] = ret
- continue
- if ret['status'] == 0:
- success.append(minion)
- else:
- fail[minion] = ret['status']
- ret = sorted(set(keys['minions']) - set(minions))
- for minion in ret:
- if re.search(reg[repo],minion):
- timedout.append(minion)
- # "succeeded: {0}, didn't run: {1}, failed: {2}"
- report = "succeeded: {0}, timed out: {1}, failed: {2}"
- if fail:
- report = report + " {3}"
- report = report.format(len(success), len(timedout), len(fail), fail)
- print report
- return report
--
To view, visit https://gerrit.wikimedia.org/r/74108
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I65bb6f104d997c2635af848daeb34f10b7e95988
Gerrit-PatchSet: 1
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Ryan Lane <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits