12
votes

I'm fairly new to Ansible and I'm trying to create a role that copies a file to a remote server. The local file can have a different name every time I'm running the playbook, but it needs to be copied to the same name remotely, something like this:

- name: copy file
  copy:
    src=*.txt
    dest=/path/to/fixedname.txt

Ansible doesn't allow wildcards, so when I wrote a simple playbook with the tasks in the main playbook I could do:

- name: find the filename
    connection: local
    shell: "ls -1 files/*.txt"
    register: myfile

- name: copy file
  copy:
    src="files/{{ item }}"
    dest=/path/to/fixedname.txt
  with_items:
   - myfile.stdout_lines

However, when I moved the tasks to a role, the first action didn't work anymore, because the relative path is relative to the role while the playbook executes in the root dir of the 'roles' directory. I could add the path to the role's files dir, but is there a more elegant way?

2
It's odd that this would work - the shell module runs remotely, but from your explanation you're finding a local filename?Ramon de la Fuente
sorry, forgot to add 'connection: local', I'll update the text.hepabolu
Would synchronize module do what you need? docs.ansible.com/synchronize_module.htmlMxx
@mxx Good point, the synchronize module would also be a solution.Ramon de la Fuente
@Mxx I've tried 'synchronize' and I've run into 2 issues: I needed to install 'rsync' on the remote site and the second is still my initial problem: the relative path is started from the root of the play-book, not the root of the role.hepabolu

2 Answers

13
votes

It looks like you need access to a task that looks up information locally, and then uses that information as input to the copy module.

There are two ways to get local information.

  • use local_action:. That's shorthand for running the task agains 127.0.0.1, more info found here. (this is what you've been using)

  • use a lookup. This is a plugin system specifically designed for getting information locally. More info here.

In your case, I would go for the second method, using lookup. You could set it up like this example:

vars:
  local_file_name: "{{ lookup('pipe', 'ls -1 files/*.txt') }}"

tasks:
  - name: copy file
    copy: src="{{ local_file_name }}" dest=/path/to/fixedname.txt    

Or, more directly:

tasks:
  - name: copy file
    copy: src="{{ lookup('pipe', 'ls -1 files/*.txt') }}" dest=/path/to/fixedname.txt    

With regards to paths

the lookup plugin is run from the context of the task (playbook vs role). This means that it will behave differently depending on where it's used.

In the setup above, the tasks are run directly from a playbook, so the working dir will be:

/path/to/project -- this is the folder where your playbook is.

If you where to add the task to a role, the working dir would be:

/path/to/project/roles/role_name/tasks

In addition, the file and pipe plugins run from within the role/files folder if it exists:

/path/to/project/roles/role_name/files -- this means your command is ls -1 *.txt

caveat:

The plugin is called every time you access the variable. This means you cannot trust debugging the variable in your playbook, and then relying on the variable to have the same value when used later in a role!

I do wonder though, about the use-case for a file that resides inside a projects ansible folders, but who's name is not known in advance. Where does such a file come from? Isn't it possible to add a layer in between the generation of the file and using it in Ansible... or having a fixed local path as a variable? Just curious ;)

8
votes

Just wanted to throw in an additional answer... I have the same problem as you, where I build an ansible bundle on the fly and copy artifacts (rpms) into a role's files folder, and my rpms have versions in the filename.

When I run the ansible play, I want it to install all rpms, regardless of filenames.

I solved this by using the with_fileglob mechanism in ansible:

- name: Copy RPMs
  copy: src="{{ item }}" dest="{{ rpm_cache }}"
  with_fileglob: "*.rpm"
  register: rpm_files

- name: Install RPMs
  yum: name={{ item }} state=present
  with_items: "{{ rpm_files.results | map(attribute='dest') | list }}"

I find it a little bit cleaner than the lookup mechanism.