0

I have the following make file:

deploy.runtime:
    kubectl describe service hello-node -n default | grep "LoadBalancer Ingress:" | awk '{print $$3}'
    $(eval MY_IP=$(kubectl describe service hello-node -n default | grep "LoadBalancer Ingress:" | awk '{print $$3}'))
    @echo IP: $(MY_IP)

When I run this, the output I get is:

35.198.222.110
IP: 

It seems like the bariable MY_IP is not being set. I've also tried running it with back ticks like this:

$(eval MY_IP=`kubectl describe service hello-node -n default | grep "LoadBalancer Ingress:" | awk '{print $$3}'`)

Which gives me the output:

IP: LoadBalancer Ingress: 35.198.222.110

I'm really confused about why awk doesn't seem to be getting the correct arguments. I'm sure it's something to do with escape arguments, but I cannot for the life of me see how.

Andy
  • 3,228
  • 8
  • 40
  • 65
  • Seems like you aren't assigning the value properly. Try this syntax: MY_IP=$(kubectl describe service hello-node -n default | grep "LoadBalancer Ingress:" | awk '{print $$3}') – Amr Keleg Jun 24 '19 at 16:23
  • Possible duplicate of [How to assign the output of a command to a Makefile variable](https://stackoverflow.com/questions/2019989/how-to-assign-the-output-of-a-command-to-a-makefile-variable) – Amr Keleg Jun 24 '19 at 16:25

2 Answers2

0

I've found that I can fix this by adding a 'shell' into the command:

$(eval MY_IP=$(shell kubectl describe service hello-node -n default | grep "LoadBalancer Ingress:" | awk '{print $$3}'))
Andy
  • 3,228
  • 8
  • 40
  • 65
0

What you probably missed is that the eval parameter is expanded by make. So, in:

$(eval MY_IP=$(kubectl describe service hello-node -n default | grep "LoadBalancer Ingress:" | awk '{print $$3}'))

the $(kubectl describe...) is expanded by make as the empty string (unless you have a make variable named kubectl describe..., which is unlikely). The eval function thus assigns the empty string to the make variable MY_IP. It does not happen with back ticks but here you hit another problem: the MY_IP variable is assigned:

`kubectl describe... | awk '{print $3}'`

not the result of its evaluation by the shell. Note that the $$3 has been transformed in $3 by make when it expanded the eval parameter. And it is when executing:

echo IP: $(MY_IP)

that make expands (recursively) the recipe that becomes, step by step:

echo IP: `kubectl describe... | awk '{print $3}'`

and then:

echo IP: `kubectl describe... | awk '{print }'`

(unless you have a make variable named 3). This is what is passed to the shell, leading to what you see. Use $$$$3, instead, and it should work as you expect... except that the make variable MY_IP is not assigned the value you wanted.

The shell function, as you noticed, solves all this but you end up with a terrible mixture: a shell command, evaluated by make through the shell function, its result assigned to a make variable thanks to the eval make function, in the middle of a recipe which is a list of shell commands. I don't know what you want to do but there must be something simpler.

For instance, if you used a make variable just because you couldn't pass a shell variable from one line of your recipe to the next (normal, they are executed by two independent shell invocations), you could reduce your recipe to a single line (but with ; \ line continuation for better readability):

deploy.runtime:
    @MY_IP=`kubectl describe... | awk '{print $$3}'`; \
    echo IP: $$MY_IP

Here, MY_IP is a shell variable and it is still available to the echo command because it is part of the same recipe line. Note the double $ sign in $$3 and $$MY_IP to escape the first expansion by make.

If, instead, you really want to use a make variable, you could assign it as a regular make variable (using back ticks or the shell function, as you wish):

MY_IP = `kubectl describe... | awk '{print $$3}'`

deploy.runtime:
    @echo IP: $(MY_IP)

or:

MY_IP = $(shell kubectl describe... | awk '{print $$3}')

deploy.runtime:
    @echo IP: $(MY_IP)

Important note about this last solution:

The MY_IP = ... assignment is a recursive (delayed) assignment, instead of a simple (immediate) assignment MY_IP := .... This means that the shell command is not expanded and executed immediately when you invoke make. It is only when make needs the value of MY_IP. So, if the deploy.runtime recipe is the only place where there is a reference to the value of MY_IP, the kubectl... will be executed only if this recipe is executed. For instance, if you have another clean target which recipe does not use the value of MY_IP, and if you invoke make clean, the kubectl... will not be executed at all. The drawback is that if you have several places where make needs its value, the kubectl... will be executed several times.

If you use the simple assignment, instead:

MY_IP := $(shell kubectl describe... | awk '{print $$3}')

the kubectl command will be executed only once but it will be each time you invoke make, even if its value is not needed.

In case this is a problem, Mad Scientist has a very nice trick to combine the pros and cons of recursive and simple assignments:

MY_IP = $(eval MY_IP := $$(shell kubectl...))$(MY_IP)

If you are interested, read his post about this. How many $ you will need for the awk command is left as an exercise...

Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51