Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F5777615
PhageRemoteWorkflow.php
No One
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Authored By
epriestley
Aug 2 2018, 10:20 PM
2018-08-02 22:20:21 (UTC+0)
Size
6 KB
Referenced Files
None
Subscribers
None
PhageRemoteWorkflow.php
View Options
<?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
;
}
}
File Metadata
Details
Attached
Mime Type
text/plain; charset=utf-8
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
1170264
Default Alt Text
PhageRemoteWorkflow.php (6 KB)
Attached To
Mode
P2107 PhageRemoteWorkflow.php
Attached
Detach File
Event Timeline
Log In to Comment