-1

I'm building a sh script to be able to run Docker containers all in once. In this way, I no need to run single containers every time. What I need to add now is a way to run my docker-compose up --build -target=<ENV> the ENV will be DEV or PROD. In this way, I can run the right environment in my Docker setup.

At this time my script looks as follow but when I try to pass $2 = $DEV is giving me an error of [: =: unary operator expected and I don't know what could be the right fix to this

#!/bin/bash

CLEAN="clean"
RUN="run"
STOP="stop"
DEV="dev"
PROD="prod"

if [ "$#" -eq 0 ] || [ $1 = "-h" ] || [ $1 = "--help" ]; then
    echo "Usage: ./myapp [OPTIONS] COMMAND [arg...]"
    echo "       ./myapp [ -h | --help ]"
    echo ""
    echo "Options:"
    echo "  -h, --help    Prints usage."
    echo ""
    echo "Commands:"
    echo "  $CLEAN      - Stop and Remove containers."
    echo "  $RUN        - Build and Run containers."
    echo "  $STOP       - Stop containers."
    exit
fi

clean() {
    stop_existing
    remove_stopped_containers
    remove_unused_volumes
}

run() {
    echo "Cleaning..."
    clean

    echo "Running docker..."
        if [ $2 = $DEV ]; then
            echo "$DEV - Running in - $DEV - environment"
            docker-compose up --build -target=$DEV
        fi

}

stop_existing() {
    MYAPP="$(docker ps --all --quiet --filter=name=wetaxitask_api_dev)"
    REDIS="$(docker ps --all --quiet --filter=name=wetaxitask_redis)"
    MONGO="$(docker ps --all --quiet --filter=name=wetaxitask_mongodb)"

    if [ -n "$MYAPP" ]; then
        docker stop $MYAPP
    fi

    if [ -n "$REDIS" ]; then
        docker stop $REDIS
    fi

    if [ -n "$MONGO" ]; then
        docker stop $MONGO
    fi
}

remove_stopped_containers() {
    CONTAINERS="$(docker ps -a -f status=exited -q)"
    if [ ${#CONTAINERS} -gt 0 ]; then
        echo "Removing all stopped containers."
        docker rm $CONTAINERS
    else
        echo "There are no stopped containers to be removed."
    fi
}

remove_unused_volumes() {
    CONTAINERS="$(docker volume ls -qf dangling=true)"
    if [ ${#CONTAINERS} -gt 0 ]; then
        echo "Removing all unused volumes."
        docker volume rm $CONTAINERS
    else
        echo "There are no unused volumes to be removed."
    fi
}

if [ $1 = $CLEAN ]; then
    echo "Cleaning..."
    clean
    exit
fi

if [ $1 = $RUN ]; then
    run
    exit
fi

if [ $1 = $STOP ]; then
    stop_existing
    exit
fi

What I want to achieve is that possible to run my sh as follow

./script.sh run dev or prod
  
Jakub
  • 2,367
  • 6
  • 31
  • 82
  • 1
    `-target` (nor `--target`) does not seem to be an option supported by [`docker-compose up`](https://docs.docker.com/compose/reference/up/) (?) – ErikMD Jul 24 '20 at 19:07
  • 1
    Also when it comes to writing shell scripts, it is strongly recommended to use the [shellcheck](https://www.shellcheck.net/) static analysis tool (also available as a [package](https://github.com/koalaman/shellcheck#user-content-installing)) to lint your scripts, and sometimes find some subtle bugs. In particular, it raises the following remark: `line 86: run → SC2119: Use run "$@" if function's $1 should mean script's $1.` – ErikMD Jul 24 '20 at 19:13
  • Yes I got that target is not under docker-compose but in my docker file I have ENV and I would like to know what should I do to run in the right environment then if you know – Jakub Jul 24 '20 at 19:19
  • 1
    Two cases: 1. you want to pass an env. variable at *build* time; or 2. override an env. variable at (container) *run* time. I guess you are maybe interested in 1. (?) → in this case you should use [`ARG variable_name`](https://docs.docker.com/engine/reference/builder/#arg) in your Dockerfile, and [`docker-compose build --build-arg="variable_name=value"`](https://stackoverflow.com/a/50734388/9164010) – ErikMD Jul 24 '20 at 19:30
  • 1
    If you are indeed interested in passing environment variables at build time, do you think your question is a duplicate of that SO question: [How to define build-args in docker-compose?](https://stackoverflow.com/q/50734271/9164010) – ErikMD Jul 24 '20 at 19:32
  • 1
    When `$1` is empty or undefined, you are effectively running `[ = "--help" ]` which is a syntax error. The fix is easy; add quotes. `if [ "$1" = "--help" ]` and similarly elsewhere. See [When to wrap quotes around a shell variable?](https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable) – tripleee Jul 24 '20 at 19:34
  • @ErikMD I'm trying number 1 but in my Dockerfile, I have ENV NODE_ENV=developpment how to use this with the ARGS I cannot figure out – Jakub Jul 24 '20 at 19:48
  • you just need to replace this line with `ARG NODE_ENV` or with `ARG NODE_ENV="dev"` if you want to have a default value (in case the `--build-arg` option is missing). Note however that this environment variable won't be kept at runtime later on. But if this is needed, you can write the following two lines: `ARG NODE_ENV` `ENV NODE_ENV="$NODE_ENV"` to have both features. – ErikMD Jul 24 '20 at 19:55
  • If you don't object one could thus mark your question as duplicate (I've just proposed a vote for that), and beyond the `--build-arg` feature we discussed above, note that your script should be revised at least in two ways: replacing `run` with `run "$@"` as I already mentioned, and replacing `$1` with `"$1"`… as mentioned by @tripleee. Both points would have been suggested by `shellcheck` anyway. – ErikMD Jul 24 '20 at 19:59

1 Answers1

1

When you invoke a shell function, it has its own argument list. The POSIX shell spec indicates:

The operands to the command temporarily shall become the positional parameters during the execution of the compound-command

So, if you define and call a shell function

print_two_things() {
  echo "dollars one is $1"
  echo "dollars two is $2"
}

print_two_things foo bar
print_two_things

$1 and $2 are the arguments to the function, not the script.

In your script, there are two errors in the run function

run() {
    if [ $2 = $DEV ]; then :; fi
}

Here, again, $2 is the second argument to the function, not the script; since you invoke this as just run with no arguments it's an empty string. Second, you don't quote either of these variables, so the empty string just gets dropped. This expands to the nonsensical [ = dev ] which produces the error you see.

You need to either capture the positional parameters in variables at the top level, or pass them down to the shell function. An example of the latter approach could be:

run() {
  environment="$1"  # first parameter _to this function_
  clean
  if [ "$environment" = "$DEV" ]; then :; fi
}

# at the top level; parameters _to the script_
if [ "$1" = "$RUN" ]; then
  run "$2"
  exit 0
fi

In a Docker context you might find it easier just to pass this in as an environment variable. Anything you set with a docker run -e option or similar settings will be directly available as environment variables in shell scripts. It's also usually considered a best practice to run identical images in dev, test, and prod if at all possible.

David Maze
  • 130,717
  • 29
  • 175
  • 215
  • In dev, I have to run Nodemon as I'm in NodeJS and in that way, I can have live reloaded on code changes. Unfortunate I cannot understand how to make that run also with this example which runs and exits the script in end without having my containers running actually and my env is ignored – Jakub Jul 24 '20 at 20:26