Home > front end >  yq create complex object based on existing file
yq create complex object based on existing file

Time:01-11

Given a docker compose file:

version: '3'
services:
  service1:
    image: image:name
    environment: 
      - TEMP_ID=1928
    volumes: 
      - type: bind
        source: local/path/to 
        target: container/path/to
    ports:
      - 8900:8580
  service2:
    image: image:name
    environment: 
      - TEMP_ID=1451
    volumes: 
      - type: bind
        source: local/path/to/1451
        target: container/path/to
    ports:
      - 8901:8580

I am limited to writing a bash script to add a service to the above template based on its content. some of the values are being directly copied from the last service written in the services array, some fields needs modification. I managed to extract and prepare all the values I need to add and I am stuck with creating the service object and add it to the existing file. The script I have so far is:

#!/bin/bash
SERVICE_NAME=$1
TEMP_ID_ARG=$2

#extract data to copy from last configured client.
SERVICES=($(yq eval '.services | keys' docker-compose.yml))
LAST_SERVICE=${SERVICES[${#SERVICES[@]}-1]}
echo "adding user based on last service: $LAST_SERVICE"

IMAGE=$(yq -P -C eval '.services["'$LAST_SERVICE'"].image' docker-compose.yml)

ENVIRONEMNT_ENTRY="TEMP_ID=${TEMP_ID_ARG}"

TARGET_PATH_IN_CONTAINER=$(yq -P -C eval '.services["'$LAST_SERVICE'"].volumes[0].target' docker-compose.yml)
VOLUME_TYPE=$(yq -P -C eval '.services["'$LAST_SERVICE'"].volumes[0].type' docker-compose.yml)

LOCAL_PATH_TO_SOURCES=$(yq -P -C eval '.services["'$LAST_SERVICE'"].volumes[0].source' docker-compose.yml)

PATH_AS_ARRAY=($(echo $LOCAL_PATH_TO_SOURCES | tr "\/" " ")) 
PATH_AS_ARRAY[${#PATH_AS_ARRAY[@]}-1]=$TEMP_ID_ARG

NEW_PATH_TO_RESOURCE=$(printf "/%s" "${PATH_AS_ARRAY[@]}")

# extract port mapping, take first argument (exposed port) increment its value by 1 (no upper limitation)
# join back together with : delimiter.
PORT_MAPING_AS_ARRAY=($(yq -P -C eval '.services["'$LAST_SERVICE'"].ports[0]' docker-compose.yml | tr ":" " "))
# NO UPPER LIMITATION FOR PORT!!!
PORT_MAPING_AS_ARRAY[0]=$(expr $PORT_MAPING_AS_ARRAY   1)
NEW_PORT_MAPPING=$(printf ":%s" "${PORT_MAPING_AS_ARRAY[@]}")
NEW_PORT_MAPPING=${NEW_PORT_MAPPING:1}

VOLUME_ENTRY=$(yq -P -C eval --null-input '.type = "'$VOLUME_TYPE'" | .source = "'$NEW_PATH_TO_RESOURCE'" | .target = "'$TARGET_PATH_IN_CONTAINER'"')
test=$(yq -P -C -v eval --null-input ' .'$SERVICE_NAME'.image = "'$IMAGE'" | .'$SERVICE_NAME'.environement = ["'$ENVIRONEMNT_ENTRY'"] | (.'$SERVICE_NAME'.volumes = ["'$VOLUME_ENTRY'"] | .'$SERVICE_NAME'.ports = ["'NEW_PORT_MAPPING'"])')
echo $test

when running, what I thought to be a working assembly command of all parts, returns the following error:

Error: cannot pass files in when using null-input flag

the expected output when calling add_service.sh service3 1234 given the above input file:

version: '3'
services:
  service1:
    image: image:name
    environment: 
      - TEMP_ID=1928
    volumes: 
      - type: bind
        source: local/path/to 
        target: container/path/to
    ports:
      - 8900:8580
  service2:
    image: image:name
    environment: 
      - TEMP_ID=1451
    volumes: 
      - type: bind
        source: local/path/to/1451
        target: container/path/to
    ports:
      - 8901:8580
  service3:
    image: image:name
    environment: 
      - TEMP_ID=1234
    volumes: 
      - type: bind
        source: local/path/to/1234
        target: container/path/to
    ports:
      - 8902:8580

As my bash skills are not so strong I welcome any advice or better solution to my problem.

CodePudding user response:

You should do it all with one yq call, passing the external values as variables; that will make the code safer and faster:

service_name="service3" \
temp_id="1234" \
environment="TEMP_ID=1234" \
yq eval '
    .services[ .services | keys | .[-1] ] as $last |
    .services[ strenv(service_name) ] = {
        "image": $last.image,
        "environment": [ strenv(environment) ],
        "volumes": [ {
            "type": $last.volumes[0].type,
            "source": (
                $last.volumes[0].source |
                split("/") | 
                .[-1] = strenv(temp_id) |
                join("/")
            ),
            "target": $last.volumes[0].target
        } ],
        "ports": [ (
            ""   $last.ports[0] |
            split(":") |
            .[0] tag = "!!int" |
            .[0]  = 1 |
            join(":") |
            . tag = ""
        ) ]
    }
' docker-compose.yml

output:

version: '3'
services:
  service1:
    image: image:name
    environment:
      - TEMP_ID=1928
    volumes:
      - type: bind
        source: local/path/to
        target: container/path/to
    ports:
      - 8900:8580
  service2:
    image: image:name
    environment:
      - TEMP_ID=1451
    volumes:
      - type: bind
        source: local/path/to/1451
        target: container/path/to
    ports:
      - 8901:8580
  service3:
    image: image:name
    environment:
      - TEMP_ID=1234
    volumes:
      - type: bind
        source: local/path/to/1234
        target: container/path/to
    ports:
      - 8902:8580
  •  Tags:  
  • Related