Creating symlinks and their parent directories

Hey guys,

After reading the file documentation a few times, doing some testing and doing some preliminary searching here I hopefully have a quick question for you.

I’m working on automating my personal dev environment with Superlumic and have moved a lot of my ~/Library/ application data to Dropbox and am symlinking them back into place.

Since I’m creating symlinks for applications that haven’t been run yet (or even installed in the case of Mac App Store apps) so their parent directories may not already exist.

It looks like I could do this as two separate tasks with a directory state as well as a link state but I was hoping there might be a way to do them both in a single task to keep my configuration as simple and clean as possible.

Is it possible to do this in one task?

`

  • name: TextExpaner Settings Directory
    file: path=/Users/cwhite/Library/Application\ Support/TextExpander/ state=directory

  • name: TextExpaner Settings Symlink
    file: src=/Users/cwhite/Dropbox/TextExpander/Settings.textexpander dest=/Users/cwhite/Library/Application\ Support/TextExpander/Settings.textexpander state=link force=yes
    `

Thanks for your help!

So I take it no one else is doing this kind of thing? I pinged Ansible on Twitter and they directed me here.

Took a different approach to this and tried to set this up as a Bash command instead and discovered that it also requires the creation of the directory structure in a separate step so I’m guessing there’s no way to make Ansible do it if it isn’t built directly into the module. Looks like I’ll just need to add a second task unless anyone else has an idea.

Hey Chris,

I typically use modules to do targeted tasks instead of trying to batch multiple operations into a single task, but in the interest of educating myself, I took a swing at putting your example into a single task. The following should give you some ideas.

  • name: a play
    hosts: all
    connection: local

tasks:

  • name: TextExpander Settings Directory
    file:
    path: “/tmp/Application\ Support/TextExpander/”
    state: “directory”

  • name: TextExpander Settings Symlink
    file:
    src: “/tmp/Dropbox/TextExpander/Settings.textexpander”
    dest: “/tmp/Application\ Support/TextExpander/Settings.textexpander”
    state: “link”
    force: “yes”

  • name: a play
    hosts: all
    connection: local

tasks:

  • name: TextExpander Settings
    file:
    path: “{{ item.path|default(omit) }}”
    state: “{{ item.state }}”
    src: “{{ item.src|default(omit) }}”
    dest: “{{ item.dest|default(omit) }}”
    force: “{{ item.force|default(omit) }}”
    with_items:
  • path: “/tmp/Application\ Support/TextExpander2/”
    state: “directory”
  • src: “/tmp/Dropbox/TextExpander2/Settings.textexpander”
    dest: “/tmp/Application\ Support/TextExpander2/Settings.textexpander”
    state: “link”
    force: “yes”

The first play is just a mock-up of the example you provided.

The second play is the same example (I just changed the directory name to “TextExpander2”) with a single task used with a loop and jinja’s omit filter. I’m looping over a list of dictionaries and using omit on values that I know may not exist. State always exists.

Now, does this make it look cleaner? Well, that’s subjective so I’ll let you decide. You could take the content in the with_items list and put it in a vars file to make the play itself look smaller (and allow your list of things to grow nearly unbounded).

When I run it on my box over here it goes off without a hitch and gives me (what I think) are the results you’re looking for

SEA-ML-RUPP1:ui trupp$ ansible-playbook -i localhost, ok.yaml

PLAY [a play] *****************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [TextExpander Settings Directory] ***************************************
changed: [localhost]

TASK: [TextExpander Settings Symlink] *****************************************
changed: [localhost]

PLAY [a play] *****************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [TextExpander Settings] *************************************************
changed: [localhost] => (item={‘path’: ‘/tmp/Application Support/TextExpander2/’, ‘state’: ‘directory’})
changed: [localhost] => (item={‘dest’: ‘/tmp/Application Support/TextExpander2/Settings.textexpander’, ‘src’: ‘/tmp/Dropbox/TextExpander2/Settings.textexpander’, ‘state’: ‘link’, ‘force’: ‘yes’})

PLAY RECAP ********************************************************************
localhost : ok=5 changed=3 unreachable=0 failed=0

SEA-ML-RUPP1:ui trupp$

Hope that helps.

-tim

Yep, that does help a lot Tim, thanks!

The variable idea makes a lot of sense and would at least let me cut down on the redundancies a bit, I am curious though if you could show how you’d do this type of thing with modules instead in a targeted tasks.

This is my first time using Ansible and I’m definitely coming at it from the Superlumic angle and working my way outward from there, I haven’t really dug into Modules yet at all.

If you’re curious, my full configuration is here.

Clarifying what I meant by modules.

When I’m writing the tasks in my playbooks, I tend to do a step-by-step list of tasks instead of trying to bundle multiple steps into a single task. So your original example (2 tasks) is generally how I would think of writing it. The module (in our examples here the “file” part) performs a single operation for each task. If I want it to do more operations, then I just call it in more tasks.

Some of this batching of work begins to become more structured in ansible 2.0’s blocks feature, but the example you showed in your initial post is how I would have written it pending no further requirements.

I also tend to go the individual task route to prevent myself from trying to be too smart for my own good. The example I provided that batches the work would cause a mutiny amongst my colleagues :slight_smile:

-tim