Search code examples
gitansiblegit-clone

Best way to clone a git repository when authenticating via ssh key forwarding


Prerequisites:

  • Host & git authentication happens via ssh key
  • ssh key forwarding is enabled
  • Every user of our team uses a dedicated user account - it is not possible to log in via headless user account

Now we want to deploy an application from a git repository. This should be simple but it's not.

- name: Clone app repo
  git:
    repo: githost:org/repo.git
    dest: /some/location
    version: HEAD
    force: yes
    ssh_opts: -o StrictHostKeyChecking=no
  notify:
    - Restart app

githost is an entry in our .ssh/config

This above task works. But the repository is (of course) cloned as the user who executed the playbook. What we need instead is:

  • All files should be owned by a headless user, let's call him zaphod
  • Only zaphod is allowed to read the files. We're talking about 0600/0700 permissions.

The following task will not work, because by using become we will lose the forwarded ssh key and therefore git authentication will fail:

- name: Clone app repo
  git:
    repo: githost:org/repo.git
    dest: /some/location
    version: HEAD
    force: yes
    ssh_opts: -o StrictHostKeyChecking=no
  notify:
    - Restart app
  become: yes
  become_user: zaphod

The following variation would first call a handler which changes ownership of the checkout before (re)starting the application:

- name: Clone app repo
  git:
    repo: githost:org/repo.git
    dest: /some/location
    version: HEAD
    force: yes
    ssh_opts: -o StrictHostKeyChecking=no
  notify:
    - Fix ownership
    - Restart app

This works once. But if you run the playbook a 2nd time the git task fails because the user who runs the play does not have permissions to modify the clone.

We have a very ugly solution:

  • Clone into /tmp/foo
  • Fix ownership of /tmp/foo
  • rm -rf /some/location
  • mv /tmp/foo /some/location
  • Finally (re)start application

The problem with this is that this would:

  • Trigger a restart every single time the playbook is executed
  • The Ansible summary shows 5 changed tasks, even though nothing happened - except the restart which was not required in the first place

I'm a bit picky here, but I do only want to have changed states if something really changed, so in a perfect world not even the git task would have a changed state. And for this I do not see a solution. Because we require that the cloned files are only accessible by zaphod - but zaphod himself is not able to clone the repo. So there has to be some manipulation is some form resulting in changes.

Any suggestions how this can be improved in a clean way? I don't want to add another 20 tasks shuffling around with temporary copies, temporarily changing permissions, comparing files manually and so forth...

Of course a custom written module would be able to deal with all this - but I'm more interested in something that does not take 2 days in development and battle testing. ;-)


Solution

  • It looks like you're trying to deploy an application/webpage by simply cloning the repo containing the stuff you need rather than needing to be able to then push any changes back to the repo from that server.

    If that's the case then you could get away with a local task to git archive the repo into a tarball or something and then use unarchive to copy the resulting archive to the target machine and unpack it. unarchive will allow you to set the permissions and ownership.

    So your play could look something like:

    - name: locally clone repo
      git:
        repo: githost:org/repo.git
        dest: /some/tmp/location
        version: HEAD
        force: yes
      delegate_to: localhost
      changed_when: false #
    
    - name: archive app repo
      command: git archive --format zip --output /path/to/archive master
        chdir: /some/tmp/location
      delegate_to: localhost
      changed_when: false
    
    - name: unarchive app repo
      unarchive:
        src: /path/to/archive
        dest: /some/location
        owner: zaphod
        mode: 0700
        creates: /some/location
      notify:
        - Restart app