Search code examples
bashvariablesmakefile

Avoid evaluation of variables in a Makefile


I'm trying to create a Makefile, recovering the credentials from AWS Secrets manager, and pass it to Skaffold, but when I recover the password and I try sending it to the function it fails, because it is being evaluated.

And it happens again when I try to pass it as variables to skaffold with PG_USER=$(PG_USER) \ PG_PASSWORD=$(PG_PASSWORD) \.

I tried fixing it with quotes, and double quotes, and adding $$ as other answers were saying, but at the end nothing worked. Not sure how to fix it, or where I could improve my skills in this area, I feel I only learn on these situations of doing, but then I'm in a hurry and don't enjoy it.

Makefile

production-release:
    $(eval creds := $(shell aws --profile $(AWS_PROFILE) secretsmanager get-secret-value --secret-id $(RDS_SECRET_ID) --query SecretString --output json))
    $(eval user := $(shell echo $(creds) | jq -r .username))
    $(eval password := $(shell echo $(creds) | jq -r .password))
    @$(MAKE) .skaffold-build PROFILE=production PG_USER=$(user) PG_PASSWORD=$(password)

Output:

/bin/bash: line 1: GPhADd-6Y: No such file or directory

The whole password is O}Kyfg|42t$~*!.w_f<GPhADd-6Y


Solution

  • I'm trying to create a Makefile, recovering the credentials from AWS Secrets manager, and pass it to Skaffold, but when I recover the password and I try sending it to the function it fails, because it is being evaluated.

    I presume you mean evaluated by the shell. Yes, it is. This is one reason why it can be problematic to forward make variables to the shell if you do not have tight control over their values. It can be hard to ensure that they are quoted appropriately.

    The Makefile you show wants to be a shell script instead. Maybe this one:

    #!/bin/sh
    
    creds=$(aws --profile "$AWS_PROFILE" secretsmanager get-secret-value --secret-id "$RDS_SECRET_ID" --query SecretString --output json)
    user=$(echo "$creds" | jq -r .username)
    password=$(echo "$creds" | jq -r .password)
    make .skaffold-build PROFILE=production PG_USER="$user" PG_PASSWORD="$password"
    

    ... or this one:

    #!/bin/sh
    
    creds=$(aws --profile "$AWS_PROFILE" secretsmanager get-secret-value --secret-id "$RDS_SECRET_ID" --query SecretString --output json)
    make .skaffold-build \
      PROFILE=production \
      PG_USER="$(echo "$creds" | jq -r .username)" \
      PG_PASSWORD="$(echo "$creds" | jq -r .password)"
    

    Note that I've converted not only creds, user, and password into shell variables, which is probably needed, but also AWS_PROFILE and RDS_SECRET_ID, which is more a function of the conversion to (only) shell script.


    If you want to have that inside a makefile, perhaps because there are also other targets in that file that are better suited for make, then don't overthink it. Either of those variations can be adapted to serve as the recipe for a make target. For example:

    production-release:
            creds=$$(aws --profile "$(AWS_PROFILE)" secretsmanager get-secret-value --secret-id "$(RDS_SECRET_ID)" --query SecretString --output json); \
            $(MAKE) .skaffold-build \
                PROFILE=production \
                PG_USER="$$(echo "$$creds" | jq -r .username)" \
                PG_PASSWORD="$$(echo "$$creds" | jq -r .password)"
    
    .PHONY: production-release
    

    Note that I have switched AWS_PROFILE and RDS_SECRET_ID back to make variables. Other than that, I escaped all $ that are to be passed through to the shell by doubling them, and I put the assignment of shell variable creds on the same logical line as the sub-make command, but as a separate shell command.


    Additional notes:

    • do not use $(eval). Especially not in recipes, but not outside them either. You can consider revisiting this advice when you've acquired enough expertise to feel safe ignoring me.

    • avoid $(shell) when you can, which should be most of the time. It's usually preferable to just write a recipe. In fact, those who use makes other than GNU make don't have $(shell) anyway, and they get along pretty well.

    • don't suppress echo of recipe lines until you're sure they're working correctly, and consider not doing so even then.