Search code examples
node.jsexpress-handlebars

Stdout from spawnSync to express-handlebars


Newbie with Node so please bear with me. Also I know this question has been asked before but I haven't figured out how to use it in my case so again, please do try to help.

What I'm trying to do - Trying to get the images for a specific docker container in list format using express-handlebars.

Code -

app.get('/restore/:userid', async (req,res) => {
  let userid = req.params.userid
  let userData = await Users.findById(userid, async function(err, data) {
    let site = await data.docker.site.slice(8)
    let backupsRun = await spawnSync('sh', ['-c', `echo redhat237 | sudo -S docker images | grep ${site} | awk '{print $2}'`], {encoding: 'utf-8'})
    let backupsStdout = await backupsRun.stdout
    if (backupsRun.status != 0) {var getBackups = new Error("Cannot fetch Backups")}
    let backupsStdoutDecoded = await unescape(encodeURIComponent(backupsStdout))
    let backupsArr = await backupsStdoutDecoded.split("\n")
    /*for (var i = 0; i < backupsArr.length; i++) {
      var backups = await backupsArr[i].value
    }*/
    let backups = backupsArr
    try {
      console.log(backups + ' Try Block')
      await res.render('restore', {
        helpers: {
          userid: function () {return req.params.userid},
          site: site,
          backups: backups
        }
      })
    } catch (e) {
      if (e.getBackups) {
        res.send(e.getBackups)
      } else {
        res.send(e)
      }
    }
  })
}) //close restore get

I can see the output in console.log but it reads as 1 line with seemingly 1 index.

test.test.org-12-7-2021T12_13,test.test.org-12-07-21T12_10,test.test.org12-07-21T12_10,test.test.org, Try Block

My handlebars page -

<h1>Restore for {{site}} - </h1>
<ul>
  {{#each backups}}
  <li><p>{{this}}</p></li>
  {{else}}
  <h3>ERROR - No Backups or we couldn't retrieve your backups</h3>
  {{/each}}
</ul>
<div>
  {{backups}} - Backups array in div
</div>

The strange thing is I can see the output in the div section which I had added for debugging, however I can't get the #each block to iterate.

From what I can understand from the following situation is that while split() is supposed to return an array of substrings for some reason that doesn't seem to be happening.

I even went and tried join() and toString() but it was the same result. If I uncomment the for loop and run it with .value it returns "undefined" in the console.log.

If someone could please point out my mistake/s and help me with it, I'd really appreciate it.


Solution

  • Move backups and site to the parent object passed to res.render

    res.render('restore', {
      helpers: {
        userid: function () {return req.params.userid},
      },
      site,
      backups,
      })
    

    Then loop on the variable `{{#backups}}

    <h1>Restore for {{site}} - </h1>
    <ul>
      {{#each backups}}
      <li><p>{{this}}</p></li>
      {{else}}
      <h3>ERROR - No Backups or we couldn't retrieve your backups</h3>
      {{/each}}
    </ul>
    <div>
      {{ backups }} - Backups array in div
    </div>
    

    More cleanup

    When are arrays are converted to a string, they are joined with a ,. The console.log(backups + ' Try Block') is doing this conversion.

    Give the applications user access to run sudo docker images without a password via sudoers (or if you must, sudo docker but understand the security implications that the app has complete control of the host now)

    Avoid using a shell when executing commands. Use dockers filter and output modifiers or process the output in JS where possible.

    Only use await when calling an async function.

    Setup an async function to run your docker commands, assuming this might be common.

    const util = require('util')
    const exec = util.promisify(require('child_process').exec)
    
    async function dockerCommand(docker_args) {
      try {
        console.log('Running docker', docker_args)
        const res = await exec(['sudo', 'docker', ...docker_args])
        return res
      } catch (e) {
        console.error('Error running docker', docker_args, e)
        throw e
      }
    }
    
    app.get('/restore/:userid', async (req, res) => {
      try {
        const userid = req.params.userid
        const userData = await Users.findById(userid)
        const site = userData.docker.site.slice(8)
        const { stdout, stderr } = await dockerCommand('images', `--filter=*:${site}*`, '--format={ .Tag }')
        const backups = stdout.split('\n')
        console.log('backups for site[%s]', site, backups)
        res.render('restore', {
          helpers: {
            userid: function () {return req.params.userid},
          },
          site,
          backups,
        })
      } catch (e) {
        console.error('Error retrieving backups', req.params.userid, e)
        res.send('Error retrieving backups')
      }
    }) //close restore get