How can I create command autocompletion for Fabric? - fabric

I created a fabric file that contain several commands from short to long and complex. I need to have an autocomplete feature so that when user type fab[tab][tab] then all available fab commands are shown, just like we have in bash.
i.e.
user#someone-ubuntu:~/path/to/fabfile$ fab[tab][tab]
command1 command2 command3 ..and so on
How can I do this ?

There are instructions you can follow here: http://evans.io/legacy/posts/bash-tab-completion-fabric-ubuntu/
Basically you run a script that calls fab --shortlist, the output gets fed into complete which is a bash function that you can read more about here: https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html

I did this for my new fabfile using fabric 2.5 & Python 3:
~/.config/fabfile
#!/usr/bin/env zsh
_fab()
{
local cur
COMPREPLY=()
# Variable to hold the current word
cur="${COMP_WORDS[COMP_CWORD]}"
# Build a list of the available tasks from: `python3 -m fabric --complete`
local cmds=$(python3 -m fabric --complete 2>/dev/null)
# Generate possible matches and store them in the
# array variable COMPREPLY
COMPREPLY=($(compgen -W "${cmds}" $cur))
}
# Assign the auto-completion function for our command.
complete -F _fab fab
And in my ~/.zshrc:
source ~/.config/fabfile
I updated the fabric 1.X version here: gregorynichola's gist

Related

Why can't I run python scripts from the command line interactive session using "./ name.py"?

I'm following along with Google's Python class, and the person in the videos always runs his scripts from the interactive session in command line using "./". Whenever I try it, I just get a syntax error. How can I use ./ to run scripts? I'm using Windows 10
To run a script from the command line you need to use the syntax
python3 script.py
Now on Unix systems, it's possible to add a shebang to the first line of the script as followings
#!/usr/bin/env python3
This then allows the shell syntax './name.py' to work. But windows doesn't have this mechanism. Instead, you need to create an 'association' between the .py extension and the python executable ('right click', 'open with'). Or just use the full syntax. Both require the python executable to be in your path, and generally on windows both python 2 and 3 will have the same executable name

Conditionals in dockerfile?

Is it possible to do the following
docker build --build-arg myvar=yes
f
RUN if ["$myvar" == "yes"]; \
then FROM openjdk \
COPY . . \
RUN java -jar myjarfile.jar \
fi
As you can tell from above i only want to run the specific section in the dockerfile if the build argument is set. I've seen similar thread but they seems to always be running bash commands. If its possible i can't seem to get the syntax correct.
As of now, doing conditional execution in Dockerfiles without the help of the shell is severely limited, see https://medium.com/#tonistiigi/advanced-multi-stage-build-patterns-6f741b852fae
The idea behind existing approaches is to use a Docker multistage build and create different stages for the different outcomes of the IF. Then, at one point, a stage to copy data from is selected based on the value of a variable.
This is an example similar to what you wrote:
# docker build -t test --build-arg MYVAR=yes .
# docker build -t test --build-arg MYVAR=no .
ARG MYVAR=no
FROM openjdk:latest as myvar-yes
COPY . /datadir
RUN java -jar /datadir/myjarfile.jar || true
FROM openjdk:latest as myvar-no
RUN mkdir /datadir
FROM myvar-${MYVAR} as myvar-src
FROM debian:10
COPY --from=myvar-src /datadir/ /
RUN ls /
Stage myvar-no is a variant with an empty /datadir. Stage myvar-yes contains the jarfile and runs it once (remove the || true for actual use, it is just that I did not provide a "real" jarfile in my local tests). Then the last stage copies from the stage myvar-${MYVAR} and invokes ls to be able to see the differences between the two variants.
I have not understand all of the question about syntax: If there are some troubles with getting the bash syntax correctly, that is possibly easier than trying to conditionally run Dockerfile statements.

`-p` is for what in creating virtual environment command of Django

Django creates virtual environment alternatively applies command:
virtualenv -p python3 .
I have typed over 30 times of the commands.
The meaning of -p is a ghost for me.To search it several times,I failed to get its explanation.
I wonder it might be out of the scope of my concepts and vocabulary.
-p sets which python interpreter to use
See the vitrualenv reference here https://virtualenv.pypa.io/en/stable/reference/

Running the "exec" command in Jenkins "Execute Shell"

I'm running Jenkins on a Linux host. I'm automating the build of a C++ application. In order to build the application I need to use the 4.7 version of g++ which includes support for c++11. In order to use this version of g++ I run the following command at a command prompt:
exec /usr/bin/scl enable devtoolset-1.1 bash
So I created a "Execute shell" build step and put the following commands, which properly builds the C++ application on the command prompt:
exec /usr/bin/scl enable devtoolset-1.1 bash
libtoolize
autoreconf --force --install
./configure --prefix=/home/tomcat/.jenkins/workspace/project
make
make install
cd procs
./makem.sh /home/tomcat/.jenkins/workspace/project
The problem is that Jenkins will not run any of the commands after the "exec /usr/bin/scl enable devtoolset-1.1 bash" command, but instead just runs the "exec" command, terminates and marks the build as successful.
Any ideas on how I can re-structure the above so that Jenkins will run all the commands?
Thanks!
At the begining of your "Execute shell" script, execute source /opt/rh/devtoolset-1.1/enable to enable the devtoolet "inside" of your shell.
Which gives:
source /opt/rh/devtoolset-1.1/enable
libtoolize
autoreconf --force --install
./configure --prefix=/home/tomcat/.jenkins/workspace/project
make
make install
cd procs
./makem.sh /home/tomcat/.jenkins/workspace/project
I needed to look up what scl actually does.
Examples
scl enable example 'less --version'
runs command 'less --version' in the environment with collection 'example' enabled
scl enable foo bar bash
runs bash instance with foo and bar Software Collections enabled
So what you are doing is running a bash shell. I guess, that the bash shell returns immediately, since you are in non-interactive mode. exec runs the the command within the shell without creating a new shell. That means if the newly opened bash ends it also ends your shell prematurely. I would suggest to put all your build steps into a bash script (e.g. run_my_build.sh) and call it in the following way.
exec /usr/bin/scl enable devtoolset-1.1 run_my_build.sh
This kind of thing normally works in "find" commands, but may work here. Rather than running two, or three processes, you run one "sh" that executes multiple things, like this:
exec sh -c "thing1; thing2; thing3"
If you require each step to succeed before the next step, replace the semi-colons with double ampersands:
exec sh -c "thing1 && thing2 && thing3"
I have no idea which of your steps you wish to run together, so I am hoping you can adapt the concept to fit your needs.
Or you can put the whole lot into a script and exec that.

Why doesn't Fabric see my .bash_profile?

In Fabric, when I try to use any alias' or functions from my .bash_profile file, they are not recognized. For instance my .bash_profile contains alias c='workon django-canada', so when I type c in iTerm or Terminal, workon django-canada is executed.
My fabfile.py contains
def test():
local('c')
But when I try fab test it throws this at me:
[localhost] local: c
/bin/sh: c: command not found
Fatal error: local() encountered an error (return code 127) while executing 'c'
Aborting.
Other Fabric functions work fine. Do I have to specify my bash profile somewhere in fabric?
EDIT - As it turns out, this was fixed in Fabric 1.4.4. From the changelog:
[Feature] #725: Updated local to allow override of which local shell is used. Thanks to Mustafa Khattab.
So the original question would be fixed like this:
def test():
local('c', shell='/bin/bash')
I've left my original answer below, which only relates to Fabric version < 1.4.4.
Because local doesn't use bash. You can see it clearly in your output
/bin/sh: c: command not found
See? It's using /bin/sh instead of /bin/bash. This is because Fabric's local command behaves a little differently internally than run. The local command is essentially a wrapper around the subprocess.Popen python class.
http://docs.python.org/library/subprocess.html#popen-constuctor
And here's your problem. Popen defaults to /bin/sh. It's possible to specify a different shell if you are calling the Popen constructor yourself, but you're using it through Fabric. And unfortunately for you, Fabric gives you no means to pass in a shell, like /bin/bash.
Sorry that doesn't offer you a solution, but it should answer your question.
EDIT
Here is the code in question, pulled directly from fabric's local function defined in the operations.py file:
p = subprocess.Popen(cmd_arg, shell=True, stdout=out_stream,
stderr=err_stream)
(stdout, stderr) = p.communicate()
As you can see, it does NOT pass in anything for the executable keyword. This causes it to use the default, which is /bin/sh. If it used bash, it'd look like this:
p = subprocess.Popen(cmd_arg, shell=True, stdout=out_stream,
stderr=err_stream, executable="/bin/bash")
(stdout, stderr) = p.communicate()
But it doesn't. Which is why they say the following in the documentation for local:
local is simply a convenience wrapper around the use of the builtin Python subprocess module with shell=True activated. If you need to do anything special, consider using the subprocess module directly.
One workaround is simply to wrap whatever command you have around a bash command:
#task
def do_something_local():
local("/bin/bash -l -c 'run my command'")
If you need to do a lot of these, consider creating a custom context manager.
It looks like you're trying to use virtualenvwrapper locally. You'll need to make your local command string look like this:
local("/bin/bash -l -c 'workon django-canada && python manage.py runserver'")
Here's an example by yours truly that does that for you in a context manager.