Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Paste
P2107
PhageRemoteWorkflow.php
Active
Public
Actions
Authored by
epriestley
on Aug 2 2018, 10:20 PM.
Edit Paste
Archive Paste
View Raw File
Subscribe
Mute Notifications
Award Token
Flag For Later
Tags
None
Referenced Files
F5777615: PhageRemoteWorkflow.php
Aug 2 2018, 10:20 PM
2018-08-02 22:20:21 (UTC+0)
Subscribers
None
<?php
final
class
PhageRemoteWorkflow
extends
PhageWorkflow
{
protected
function
didConstruct
()
{
$this
->
setName
(
'remote'
)
->
setExamples
(
'**remote** --hosts __hosts__ [__options__] -- __command__'
)
->
setSynopsis
(
pht
(
'Run a "bin/remote" command on a group of hosts.'
))
->
setArguments
(
array
(
array
(
'name'
=>
'hosts'
,
'param'
=>
'hosts'
,
'help'
=>
pht
(
'Run on hosts.'
),
),
array
(
'name'
=>
'pools'
,
'param'
=>
'pools'
,
'help'
=>
pht
(
'Run on pools.'
),
),
array
(
'name'
=>
'limit'
,
'param'
=>
'count'
,
'help'
=>
pht
(
'Limit parallelism.'
),
),
array
(
'name'
=>
'throttle'
,
'param'
=>
'seconds'
,
'help'
=>
pht
(
'Wait this many seconds between commands.'
),
),
array
(
'name'
=>
'args'
,
'wildcard'
=>
true
,
'help'
=>
pht
(
'Arguments to pass to "bin/remote".'
),
),
array
(
'name'
=>
'timeout'
,
'param'
=>
'seconds'
,
'help'
=>
pht
(
'Command timeout in seconds.'
),
),
));
}
public
function
execute
(
PhutilArgumentParser
$args
)
{
$hosts
=
$args
->
getArg
(
'hosts'
);
$pools
=
$args
->
getArg
(
'pools'
);
if
(!
strlen
(
$hosts
)
&&
!
strlen
(
$pools
))
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Provide a list of hosts to execute on with "--hosts", or a '
.
'list of host pools with "--pools".'
));
}
$remote_args
=
$args
->
getArg
(
'args'
);
if
(!
$remote_args
)
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Provide a remote command to execute.'
));
}
$limit
=
$args
->
getArg
(
'limit'
);
$throttle
=
$args
->
getArg
(
'throttle'
);
$timeout
=
$args
->
getArg
(
'timeout'
);
$hosts
=
$this
->
expandHosts
(
$hosts
,
$pools
);
$plan
=
new
PhagePlanAction
();
$local
=
new
PhageLocalAction
();
if
(
$limit
)
{
$local
->
setLimit
(
$limit
);
}
if
(
$throttle
)
{
$local
->
setThrottle
(
$throttle
);
}
$plan
->
addAction
(
$local
);
$bin_remote
=
PhacilityCore
::
getCorePath
(
'bin/remote'
);
$commands
=
array
();
foreach
(
$hosts
as
$host
)
{
$host_args
=
$remote_args
;
array_splice
(
$host_args
,
1
,
0
,
array
(
$host
));
$command
=
csprintf
(
'%R %Ls'
,
$bin_remote
,
$host_args
);
$execute
=
id
(
new
PhageExecuteAction
())
->
setLabel
(
$host
)
->
setCommand
(
$command
);
if
(
$timeout
)
{
$execute
->
setTimeout
(
$timeout
);
}
$commands
[]
=
$execute
;
$local
->
addAction
(
$execute
);
}
$t_start
=
microtime
(
true
);
$plan
->
executePlan
();
$t_end
=
microtime
(
true
);
$done
=
pht
(
'DONE'
);
echo
tsprintf
(
"
\n
-< %s >%s
\n\n
"
,
$done
,
str_repeat
(
'-'
,
80
-
(
5
+
strlen
(
$done
))));
$okay_count
=
0
;
foreach
(
$commands
as
$command
)
{
$exit_code
=
$command
->
getExitCode
();
if
(
$exit_code
!==
0
)
{
echo
tsprintf
(
"**<bg:red> [%s] </bg>** %s
\n
"
,
$command
->
getLabel
(),
pht
(
'Command failure (%d).'
,
$exit_code
));
}
else
{
$okay_count
++;
}
}
if
(
$okay_count
===
count
(
$commands
))
{
echo
tsprintf
(
"**<bg:green> %s </bg>** %s
\n
"
,
pht
(
'COMPLETE'
),
pht
(
'Everything went according to plan (in %sms).'
,
new
PhutilNumber
(
1000
*
(
$t_end
-
$t_start
))));
}
}
private
function
expandHosts
(
$spec
,
$pools
)
{
$parts
=
preg_split
(
'/[, ]+/'
,
$spec
);
$parts
=
array_filter
(
$parts
);
$hosts
=
array
();
foreach
(
$parts
as
$part
)
{
$matches
=
null
;
$ok
=
preg_match_all
(
'/(
\d
+-
\d
+)/'
,
$part
,
$matches
,
PREG_OFFSET_CAPTURE
);
// If there's nothing like "001-12" in the specification, just use the
// raw host as provided.
if
(!
$ok
)
{
$hosts
[]
=
$part
;
continue
;
}
if
(
count
(
$matches
[
1
])
>
1
)
{
throw
new
Exception
(
pht
(
'Host specification "%s" is ambiguous.'
,
$part
));
}
$match
=
$matches
[
1
][
0
][
0
];
$offset
=
$matches
[
1
][
0
][
1
];
$range
=
explode
(
'-'
,
$match
,
2
);
$width
=
strlen
(
$range
[
0
]);
$min
=
(
int
)
$range
[
0
];
$max
=
(
int
)
$range
[
1
];
if
(
$min
>
$max
)
{
throw
new
Exception
(
pht
(
'Host range "%s" is invalid: minimum is larger than maximum.'
,
$match
));
}
if
(
strlen
(
$max
)
>
$width
)
{
throw
new
Exception
(
pht
(
'Host range "%s" is invalid: range start does not have enough '
.
'leading zeroes to contain the entire range.'
,
$match
));
}
$values
=
range
(
$min
,
$max
);
foreach
(
$values
as
$value
)
{
$value
=
sprintf
(
"%0{$width}d"
,
$value
);
$host
=
substr_replace
(
$part
,
$value
,
$offset
,
strlen
(
$match
));
$hosts
[]
=
$host
;
}
}
if
(
strlen
(
$pools
))
{
$bin_remote
=
PhacilityCore
::
getCorePath
(
'bin/remote'
);
list
(
$stdout
)
=
execx
(
'%R list-hosts %R'
,
$bin_remote
,
// NOTE: This is a dummy argument, but "bin/remote" currently requires
// a host target even if we're just going directly to the bastion. This
// could be cleaned up at some point.
'bastion-external.phacility.net'
);
$pool_hosts
=
phutil_json_decode
(
$stdout
);
$prefixes
=
preg_split
(
'/[, ]+/'
,
$pools
);
$prefixes
=
array_fill_keys
(
$prefixes
,
array
());
foreach
(
$pool_hosts
as
$pool_host
)
{
$host
=
$pool_host
[
'host'
];
foreach
(
$prefixes
as
$prefix
=>
$host_list
)
{
if
(
preg_match
(
'(^'
.
preg_quote
(
$prefix
).
'
\d
)'
,
$host
))
{
$prefixes
[
$prefix
][]
=
$host
;
}
}
}
foreach
(
$prefixes
as
$prefix
=>
$host_list
)
{
if
(!
$host_list
)
{
throw
new
Exception
(
pht
(
'Pool "%s" matched no hosts. Use real pools which contain '
.
'actual hosts.'
,
$prefix
));
}
foreach
(
$host_list
as
$host
)
{
$hosts
[]
=
$host
;
}
}
}
return
$hosts
;
}
}
Event Timeline
epriestley
created this paste.
Aug 2 2018, 10:20 PM
2018-08-02 22:20:21 (UTC+0)
emiraga
mentioned this in
T2794: Implement Phage (like Hypershell)
.
Oct 21 2021, 6:37 PM
2021-10-21 18:37:50 (UTC+0)
Log In to Comment