How can selectively run build commands per site?

Hello, I have a DO droplet provisioned with Trellis. There is 4 small websites in production on that droplet. I want to run project_pre_build_commands_local and project_local_files only for site1.com. How would I do that? Right now, those commands are run for every deploy no matter which site. I tried to split wordpress_sites in two files, but that did not work when provisioning the droplet as the last loaded group_vars’ wordpress_sites overrides the other.

hosts/production:

[web]
104.131.11.111

[production:children]
web

group_vars/production:

mysql_root_password: …

project_pre_build_commands_local:

  • path: “{{ project.local_path }}/web/app/themes/sage”
    cmd: npm install
  • path: “{{ project.local_path }}/web/app/themes/sage”
    cmd: bower install
  • path: “{{ project.local_path }}/web/app/themes/sage”
    cmd: gulp --production

project_local_files:

  • name: compiled theme assets
    src: “{{ project.local_path }}/web/app/themes/sage/dist”
    dest: web/app/themes/sage

wordpress_sites:
site1.com:

site3.com:

site4.com:

site5.com:

I ran into this same issue this weekend and @swalkinshaw suggested defining a host_vars directory for each host

I think the host_vars directory would be applicable if you had multiple DO droplets…

host_vars/
  vars_file_for_droplet_1
  vars_file_for_droplet_2

I don’t think host_vars applies here because @patrickv has only one server (ansible “host”), with multiple sites on that server.

People may have more variability in their customizations to the deploy “build commands” than other parts of trellis, so it is hard to set defaults that people won’t need to adjust. In your case, @patrickv here’s what I would try first. I’m not sure it would work (i.e., untested).

You have one set of build commands that you want run on only one of your sites. I would instruct the build commands definition to get its list of commands from a variable in the wordpress_sites dictionary, per site. If the list is missing for any given site, just use the default empty list: []

project_pre_build_commands_local: "{{ project.pre_build_commands_local | default([]) }}"
project_local_files: "{{ project.local_files | default([]) }}"

wordpress_sites:
  site1.com:
    ⋮
    pre_build_commands_local:
      - path: "{{ project.local_path }}/web/app/themes/sage"
        cmd: npm install
      - path: "{{ project.local_path }}/web/app/themes/sage"
        cmd: bower install
      - path: "{{ project.local_path }}/web/app/themes/sage"
        cmd: gulp --production
    local_files:
      - name: compiled theme assets
        src: "{{ project.local_path }}/web/app/themes/sage/dist"
        dest: web/app/themes/sage
  site3.com:
    ⋮
  site4.com:
    ⋮
  site5.com:
    ⋮

To round this out, the approach above works well for someone whose build commands will usually differ per site. Consider the other case, however, in which a person’s build commands will always be the same, but they want to run them for just some of their sites. The approach above would require repeating the same set of commands for multiple sites, which isn’t very DRY. In that case, I’d suggest adding a ternary filter like this (also untested):

pre_build_commands_local:
  - path: "{{ project.local_path }}/web/app/themes/sage"
    cmd: npm install
  - path: "{{ project.local_path }}/web/app/themes/sage"
    cmd: bower install
  - path: "{{ project.local_path }}/web/app/themes/sage"
    cmd: gulp --production

local_files:
  - name: compiled theme assets
    src: "{{ project.local_path }}/web/app/themes/sage/dist"
    dest: web/app/themes/sage

project_pre_build_commands_local: "{{ project.run_pre_build_commands_local | ternary(pre_build_commands_local, []) }}"
project_local_files: "{{ project.copy_local_files | ternary(local_files, []) }}"

wordpress_sites:
  site1.com:
    ⋮
    run_pre_build_commands_local: true
    copy_local_files: true
  site3.com:
    ⋮
    run_pre_build_commands_local: false
    copy_local_files: false
  site4.com:
    ⋮
    run_pre_build_commands_local: true
    copy_local_files: true
  site5.com:
    ⋮
    run_pre_build_commands_local: false
    copy_local_files: false

There could be variations on the above. The above keeps your logic in your vars config files, but you I suppose you could make a given build commands task conditional by adding things like when: project.run_pre_build_commands_local. See how some other tasks in that task list use the when: conditional.

2 Likes

Thanks for the detailed solution. I tested both of them in order to understand a bit more how to work with trellis and ansible. Both worked.

Adding the pre_build_commands_local to the wordpress_sites worked fine except that I had to change {{ project.local_path }} to an explicit directory. Would it be possible to reference a variables from the current array in that case? local_path is declared just before pre_build_commands_local. Anyhow I got it working, I am taking the opportunity to learn. :smile:

As for adding when: project.run_pre_build_commands_local, would it be possible to change the deploy task you specify without changing the file in the roles directory? Can I redefine a task or add the conditional from… my group_vars/development file?

1 Like

That’s the very type of question that made me give the caveat “I’m not sure it would work (i.e., untested).” I still haven’t tested, but just left it to you. Sounds like you’re doing great. Nice work!

I don’t think there is a way to explicitly tell the task to skip without adding the when: parameter (changing the file in the roles directory).

However, the tasks will skip on their own if an empty list is passed to the with_items: parameter. So, I tried to conditionally pass empty lists in the second main section above, using the ternary filter.

Maybe it’s already clear, but

project_local_files: : "{{ project.copy_local_files | ternary(local_files, []) }}"

says if the current site’s variable copy_local_files: true then return the previously defined local_files, otherwise just return an empty list: [].

Maybe to make it look more intuitively conditional you could write something like this (untested):

project_local_files: : "{% if project.copy_local_files | bool %}{{ local_files }}{% else %}[]{% endif %}"

Maybe you knew that too. Anyway, the ternary filter feels more succinct to me.

If you find yourself enjoying the possibilities of jinja filters in ansible, check out jinja’s built-in filters and some special ansible filters. More generally the ansible docs that you’ve probably already seen are quite good, and this ansible-quickref is a nice resource when you’re looking for that type of ansible information. Enjoy!

1 Like

Just wanted to thank you again for your help.

I actually ended up doing this in order to have the project_pre_build_commands_local and project_local_files defined per site in the wordpress_sites array.

project_pre_build_commands_local: “{{ project.pre_build_commands_local | default() }}”
project_local_files: “{{ project.local_files | default() }}”

With that I can define the commands like this :

wordpress_sites:
site…com:
[…]
pre_build_commands_local:
- path: “{{ project.local_path }}/web/app/themes/sage”
cmd: npm install
- path: “{{ project.local_path }}/web/app/themes/sage”
cmd: bower install
- path: “{{ project.local_path }}/web/app/themes/sage”
cmd: gulp --production
local_files:

  • name: compiled theme assets
    src: “{{ project.local_path }}/web/app/themes/sage/dist”
    dest: web/app/themes/sage
4 Likes