perlでNet::OpenSSHを使って、他サーバーへsudo su -で管理者になって、コマンドを実行する(ポエム有)

<details><summary>反省ポエム</summary>

俺は常に追い込まれていた。
まぁこれはいい、俺は追い込まれるのが好きなんだ。(いい迷惑)

気が付いたら、perlで他サーバーへ管理者権限でコマンドを実行する必要性が発生した。
最初に想定していたのはもっと簡単だった。
他サーバーへ、ファイルのチェック、ファイルのバックアップコピー、ファイルの変更をやるだけのスクリプトを組むはずだった。
結果としては相当にキモイことになった。
対象のファイルに管理者権限のファイルがあった。管理者権限で直接ログインできるだろうと思っていたけど、本番環境で普通はできないみたいだった。(常識知らず)
しかも判明したのはコードレビュー前での最終的な動作確認時(環境が本番により近い)であった。(まぁ実際のところ、俺は当然動くと思っていたから、コードをきれいにする時間や細かな修正(は？)する時間も含んでいるんだけどね！あたしって、ほんとバカ・・・)

当然、スケジュールは間に合わないので上司に報告した。(こういうことが出来るようになった点で俺は少し成長していると思う。つまりどれだけ俺が無能かは想像しないでほしい。)
しかし、アホな俺はまだ楽観視をしていた。(管理者権限の問題ともう一つ処理の問題を発見していたにもかかわらずだ。)
sudo su - -cで簡単に管理者権限でコマンド実行できるもんね！と調子に乗っていたのだ。（ちなみに問題調査のため少し残業していて、残業ハイであった)
俺の追い込まれ好きの性格も災いし、キツキツなスケジュールを組み直し上司へ報告して帰宅した。(なんか上司も残業していたけど、俺のせいだろうか・・・)

次の日に、俺は管理者権限の問題をすぐ解決できるとして一旦置いて(意味不明な行動)、もう一点あった問題に取り組んでいた。
そちらも問題なく解決出来そうな見込みになったので、管理者権限の問題をパパっとやろうとした。
うん、そこで俺は気が付いた。sudoによる実行できるコマンドは普通は制限されていることに。
俺は深く絶望した。この二つの問題を1日で修正してコードレビューを出さないといけないのに(+αも込み)、致命的な問題を再発見してしまったのだ。(発見したものを発見した。つまり再発見である。)
結論から言うと、終わらなかったし、上司へ報告はしなかった。(つーか、できない。流石に昨日の今日でリスケするのは、俺の溜めてきた少ない人間関係経験値では無理だった。)
つまりどうするかというと、GWでこの問題の解決策を見つけ出す必要があった。(コードレビュー予定日より前にレビュー物を出すのがセオリーだが、コードレビュー初日内に出す、これをスケジュール貫通拳と呼ぶ。←効果：自分は死ぬ)
なんというか、作業の進め方として最悪すぎるし、終わっているし、開発業界本当に辞めようと思っている。
自分や周りが辛いのもそうだけど、向いていないの一言なんだ。

</details>

↑これは読まなくていいです。

# 前提

まず、一つあるのはスイッチして管理者権限になるのなら、Expect.pmを使えばいいと。(よく分からないけど)
俺が理解していないのか分からないけど、恐らくNet::OpenSSHにExpect含まれてないよね？
前提として、サーバーは何もいじらない。会社の人が使ったという実績がある、Net::OpenSSHのみでイク！という作業だ。

# 試行錯誤
まず、お世話になったページをひとつ

https://metacpan.org/pod/Net::OpenSSH

公式マニュアルです。sudoで実行する方法を書いてあって、簡単に解決できそうと思った内容です。

うーん、じゃあsu -するにはどうればいいんだ？（無知)となって参考になったページ

https://www.perlmonks.org/?node_id=1087830

このサイト、Q&Aサイトのようなんですが、見方がよく分からない・・・。
適当に情報を拾い食いしつつ動かす。

ローカルでは動いたんで、su - -cでいいやと思ったけど、サーバー設定で無理だったので(絶望)
ほぼ諦めつつ、ワンcaptureで複数コマンド叩けねえかなと検索していたら見つけた内容

https://stackoverflow.com/questions/20621897/resolving-issue-with-netopenssh-and-passing-multiple-commands-to-a-router

開発者のSALVAさんが回答している内容で、stdin経由で複数コマンド流せば動くよというようなこと翻訳したらわかった。
行ける行けるぞ！となったけど、ttyエラーメッセージが出て再度絶望。
でも、俺のローカル環境のテストプログラムでは動いたんだけどな？
となって、調べると、ttyのエラーメッセージは設定起因であることが分かる。

http://fishrimper.blogspot.com/2012/09/ssh-sudo.html

非常に分かりやすい。そして私はすべてを理解した。
これは無理だと・・・。
回避策2と回避策3でうまくいった場合は、tty=1の設定もいらず
tty=1の設定が要らなければ、複数コマンドを実行できた。
しかしDefaults requirettyという設定がされていて、回避策1でtty=1を強制されると、無理であった。

上司へ、このスクリプトはゴミで、今までかけた工数は全部無駄でした。という
考えられる中でも最悪な報告をしなければ行けないことを考え、クビより酷いことって何かあるのかな・・・と。
考えながら藁にも縋るような気持ちで検索していると、運命的な出会いをした。

https://blog.kokuyouwind.com/archives/672/

非常にいいタイトルしていますね！

そうか・・・パイプか・・・
神に感謝。まどマギにも感謝。

https://stackoverflow.com/questions/27494764/netopenssh-command-remote-with-multi-pipeline

全て調べて解決する。(公式サイトに書いてあるのに)

# 完成

試行錯誤しつつ、完成した。
その際には以下のコードでsshコマンドが何を実行しているのか確認した。
```perl:
BEGIN {
  open my $out, '>', './debug.txt' or warn $!;
  $Net::OpenSSH::debug_fh = $out;
  $Net::OpenSSH::debug = -1;
}
```

そして我のコードがこちら。(実行部分)
いまいち、シングルクォートで括る意味が分かっていない。

```perl:
my %opts = (master_opts => [-o => "StrictHostKeyChecking=no", ], master_stderr_discard => 1);
my $ssh = Net::OpenSSH->new($server_name,user => $host_name, %opts, key_path  => $key, timeout => 20);
if($ssh->error){
  $ssh->capture('exit');
  exit(1);
}

my $out = $ssh->capture({tty =>1, stdin_data => "$sudo_passwd\n"},
                           'echo', 'id;pwd', \\'|',
                           'sudo',
                           '--',
                           'su', '-');

print "out : " . $out;
print "\n";

if($ssh->error){
  print "管理者操作失敗\n";
  $ssh->capture('exit');
  exit(1);
}
exit(0);
```

懸念点はある。
・これ動作確認で動くのだろうか(バージョンの違いもある)
・こういうコマンドの叩き方して大丈夫だろうか
・私はコードレビューに間に合うのだろうか