[Snark] Docker Node Bash

Christopher Vollick 0 at psycoti.ca
Sat Nov 17 23:16:23 UTC 2018

On Sun, Oct 28, 2018 at 09:41:17PM -0500, Sean Howard wrote:
> piglatin.sh exists to support the piglatin docker container service.
> It requires an external pig latin service to be running, but a docker container is provided to support local development.
> .piglatinrc MUST be in the form
> `HOST=hostname
> PORT=portname`
> If you must either of these then the defaults will be used. Other imports are ignored.
> Requirements:
> 	This requires a standard POSIX bash shell with dos2unix installed to support removing CRLFs from webserver

I like a good set of unjustified requirements and instructions, any of which might screw something up in subtle ways if ignored.
People always read READMEs, right?

> == docker-compose.yml ==
> version: '2'
> services:
>  pig:
>    image: node
>    build:
>      context: .
>      dockerfile: ./Dockerfile
>    ports:
>      - "8124:8124"

I think dockerfile and maybe context are redundant in that they respecify the defaults.
But more importantly, obviously, is the complexity of using Docker for the problem.

> == Dockerfile ==
> FROM node
> RUN npm install pig-latin
> ADD piglatin.js piglatin.js
> CMD ["node", "piglatin.js"]

Right, so, we're running node and installing a third-party pig-latin package to do our work for us.
So far so good.

> == piglatin.js ==
> var piglatin = require('pig-latin');
> var http = require('http');
> server = http.createserver(function (req, res) {
> 	res.write(piglatin(req.url.replace("/","")));
> });
> 	server.listen(8124, "");
> console.log('server running at');

Huh... we've got this node library... but I have to access it from externally.
I guess it's an HTTP service now!

Also, hard-coded assumption that we're running a localhost even though it's bound to

Probably not important.

> == piglatin.sh ==
> #!/bin/bash

I think besides `source` which could have used `.`, nothing in here requires bash over /bin/sh
Doesn't mean it doesn't happen, though!

> #Should be configurable for users without a local piglatin service
> # set a default hostname
> HOST=localhost
> # set a default port
> PORT=8124
> # if the .piglatinrc file exists, use this instead of the default
> if [ -f ~/.piglatinrc ]; then
> source ~/.piglatinrc
> fi

First, we're checking the file exists but not if it's readable.
Super common, though.

More importantly, the documentation above says anything except host and port will be ignored in that file.
Obviously that's not true, though, since it's actually just sourcing the whole file.

There could be commands in there, or other variables.
Anything, really.

> # Since the container will only do one word at a time, parse the words out
> for PIG in $@

Leaving off the quotes so the shell will do the splitting for you, but using $@ instead of $* anyway.

> do
> if [ ${PIG:0:2} = "yt" -o ${PIG:0:2} = "xr" ]
> then
> 	export DEMANGLE="${PIG:0:2}"
> 	PIG=`echo $PIG| sed -e "s/^yt/e/" -e "s/^xr/e/"`
> fi

I like that we pulled in a pig-latin package to do the work for us, but it ended up having different rules than our requirements.
So we were like "Well, it's just this one case" so we're using our wrapper script from the bash side to change the output to be the way we want.

> #this is the easiest way to get the correct line from the container
> WORD=`printf "GET /$PIG HTTP/1.1\n\nclose\n\n"|nc $HOST $PORT|dos2unix|tail -n3|head -n1|sed "s/way$/ay/"`

We need `dos2unix`, but can't depend on `curl`.
We do have an undeclared dependency on `nc` though, so we can build our own HTTP.

We also appear to have another fixup here inline.

> if [ "${WORD:0:1}" = "u" ]
> then
> 	if [ "${WORD: -3}" = "qay" ]
> 	then
> 		echo $WORD | sed -e "s/qay/quay/" -e "s/^u//"
> 		RETURN=1
> 	fi
> fi

Another ugly fixup, but this time it isn't changing WORD, it's just printing out right here.
But then it's not skipping here, it's just setting RETURN which actually does nothing except skip the next block.

We still have more work to do!

> if [ $RETURN = 0 ]
> then
> 	if [ -n "$DEMANGLE" ]
> 	then
> 		WORD=`echo $WORD | sed -e "s/^./$DEMANGLE/"`

Right, so, we know it was going to get mangled because we set this earlier.
We also picked "e" as the letter we want the input word to start with because it doesn't get mangled.

Then we have to use this regex to strip that back off, and put our letters back in its place.

> 	echo $WORD

And then we print the word, because we didn't do it already, because RETURN=0

> # the container only has one worker so treat it slowly
> sleep 1

Nice! We don't want our script to run too fast.
That'd break it.

> # merge script output
> done | tr "\n" " "|sed "s/ $//"

Right! All those pesky outputs above were on their own lines.
We don't want that, so we'll just jam them together at the end!
But then there's too many spaces.

All better.

All in all, I like the approach of this one.
It reflects a different kind of badcode from the others.
They tend to represent pattern that people use to confuse their logic.

This one, though, is instead technologies and libraries that are meant to promote reuse and make our lives easier, but then we have to go through a bunch of hacks to try and get it that final 5% of the way to a complete solution.

I'm sure it seemed like such a good idea at the time. Just one little fix. Just got to tweak that. Oh, just one more thing.

More information about the Snark mailing list