Chef Push-Jobsを利用して、任意コマンドのリモート実行＆出力の取得

#目的
Push-Jobsを利用すると、ワークステーション環境から、事前定義したコマンドの実行をnodeに指示できます。
Push-Jobsの基本的な説明は以下をご参照ください。
[https://docs.chef.io/push_jobs.html](https://docs.chef.io/push_jobs.html)

Push-Jobsを利用したところ、個人的に以下2点が課題になりました。

・ジョブの事前定義が必要で、任意のコマンドを実行できない
・nodeでジョブ実行した際の出力を確認できない
（"output"オプションで出力を取得できる仕様ですが、現時点ではSJISの2バイト文字が含まれると取得できなかったり、取得できる出力のサイズ上限が固定であったりします。）

そこで、カスタマイズを行い課題を解消します。

#構成
Chef Server 12.17.15
Push Jobs Server 2.2.8
Chef Client 13.4.24
Push Jobs Client 2.5.6
node Platform : AIX7, RHEL7, Windows2016


#手順
### 1. Chef Server nginxのconfファイルを追加

/var/opt/opscode/nginx/etc/nginx.d/export.conf

```
server {
    listen 60080;
    root /tmp/push-jobs;
    location = /log {
        client_max_body_size 1m;
        client_body_buffer_size 1m;
        content_by_lua_block {
            ngx.req.read_body()  -- explicitly read the req body
            local data = ngx.req.get_body_data()
            if (not data) then
                ngx.say("no body in request.")
                return
            end
            local file = string.match(data, 'filename="(.+)"')
            if (not file) then
                ngx.say("no filename in request body.")
                return
            end
            local logdir = "/tmp/push-jobs/log"
            local status, err = pcall(os.execute, "mkdir -p "..logdir)
            local filepath = logdir.."/"..file
            local f, err = io.open(filepath, "w")
            if err then
                ngx.say("can not open file on chef server: ", filepath)
                return
            end
            f:write(data)
            f:close()
            ngx.say("output data to file on chef server: ", filepath)
        }
    }
}
```

nginxを再起動

```
chef-server-ctl restart nginx
```

### 2. nodeのwhiteにコマンドを登録

AIX,Linux

```
        "script-exec-simple": "chmod 755 ${CHEF_PUSH_JOB_FILE} && ${CHEF_PUSH_JOB_FILE} ${CHEF_PUSH_JOB_ID} ${CHEF_PUSH_NODE_NAME}",
```

Windows

```
        "script-exec-simple": "powershell -Command \"mv %CHEF_PUSH_JOB_FILE% %CHEF_PUSH_JOB_FILE%.PS1; %CHEF_PUSH_JOB_FILE%.PS1 %CHEF_PUSH_JOB_ID% %CHEF_PUSH_NODE_NAME% \"",
```

### 3. Chefサーバに以下のディレクトリ・ファイルを作成

ディレクトリ作成
/tmp/push-jobs/log/
/tmp/push-jobs/script/

ファイル作成
/tmp/push-jobs/script/tmplate-lin

```
jobId=$1
nodeName=$2
logfile=/tmp/${jobId}-${nodeName}.log

cd /tmp
curl -O -k http://ChefServer:60080/script/tmpCommand
curl -O -k http://ChefServer:60080/script/http_file_upload.rb
chmod 755 tmpCommand http_file_upload.rb

LANG=C echo "#logfile head" > $logfile
LANG=C /tmp/tmpCommand  >> $logfile 2>&1
RC=$?
LANG=C echo '#logfile end' >> $logfile
echo RC=${RC} >> $logfile

ruby /tmp/http_file_upload.rb $logfile
```

/tmp/push-jobs/script/tmplate-win

```
Param( $jobId, $nodeName )
$logfile="C:/tmp/${jobId}-${nodeName}.log"
$httpUploadRb="C:/tmp/http_file_upload.rb"

Set-Location "C:/tmp/"
Invoke-WebRequest http://ChefServer:60080/script/tmpCommand -OutFile tmpCommand.ps1
Invoke-WebRequest http://ChefServer:60080/script/http_file_upload.rb -OutFile http_file_upload.rb
echo "#logfile head" | Out-File ${logfile} -Encoding UTF8
.tmpCommand.ps1 2>&1 | Out-File ${logfile} -Encoding UTF8 -Append
${RC}=$LastExitCode
echo '#logfile end' | Out-File ${logfile} -Encoding UTF8 -Append
echo "RC=${RC}" | Out-File ${logfile} -Encoding UTF8 -Append
C:/opscode/chef/embedded/bin/ruby C:/tmp/http_file_upload.rb ${logfile}
```

/tmp/push-jobs/script/http_file_upload.rb

```
require 'net/http'
require 'uri'
require 'base64'

uri = URI.parse('http://ChefServer:60080/log')
sendString = Base64.urlsafe_encode64(open(ARGV[0]).read)
sendFileName = File.basename(ARGV[0])
data = [[ "upfile", sendString, { filename: sendFileName, content_type: "application/octet-stream" } ]]

http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path)

req.set_form(data, "multipart/form-data")
res = http.request(req)

puts res.code
puts res.msg
puts res.body
```


/tmp/push-jobs/script/file_decode.rb

```
require 'base64'
puts Base64.urlsafe_decode64(open(ARGV[0]).read)
```


以上で準備完了です。

### 4. 実行してみます。

まず、実行したいコマンドを以下のファイルに記載します。

```
/tmp/push-jobs/script/tmpCommand
```

以下のようにジョブを実行します。

```
knife job start script-exec-simple <node name> --file /tmp/push-jobs/script/tmplate-lin
```

ジョブ終了後、以下のファイルがChefサーバに作成されているはずです。

```
/tmp/push-jobs/log/<jobId>-<nodeName>.log
```

取得したファイルから必要な個所を抜き出し、base64でデコードすれば、出力を確認できます。

```
grep "I2xvZ2ZpbGUgaGVhZA" /tmp/push-jobs/log/<jobId>-<nodeName>.log | sed -z 's/\r\n//g' > /tmp/push-jobs/log/<jobId>-<nodeName>.log
ruby /tmp/push-jobs/script/file_decode.rb /tmp/push-jobs/log/<jobId>-<nodeName>.log
```
※I2xvZ2ZpbGUgaGVhZAは"#logfile head"をencodeした文字列です。


#感想
こんなことするなら、シンプルにSSH/winrm 使いますよね。。。