Syncing Static Sites with Pulumi

Real talk, I still haven't successfully decoupled myself from Digital Ocean (though my Bluehost subscription is finally toast) 🤦‍♀️ like I said I would, but am starting to deploy more projects using Hetzner and Pulumi. However, I recently ran into the issue of indicating changes in a resource (my static site folder) to trigger Pulumi to update the resource when I run pulumi up.

     Type                 Name                                      Plan     
     pulumi:pulumi:Stack  dev-dev                                            

Resources:
    7 unchanged

Nothing changes, because there's no way for Pulumi to detect that the underlying resource has changed with the current configuration.

If I was using an Amazon S3 bucket, Azure Blob Storage, or Google Cloud Storage, I could use a Synced Folder in Pulumi as the resource, but since I'm deploying to Hetzner with fewer guardrails, I don't have such niceties.

The approach I settled on was to utilize the most recent commit hash from git as a property to track, so I'm still tied to only updating when deploying committed changes.

I added a helper function get_git_commit_hash to __main__.py in my Pulumi project

def get_git_commit_hash(directory):
    try:
        result = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            capture_output=True,
            text=True,
            check=True,
            cwd=directory,
        )
        return result.stdout.strip()
    except Exception as e:
        print(f"Warning: Could not get git commit hash from project: {e}")
        return "unknown-commit"

then modified the site_sync resource to the following:

site_sync = command.local.Command(
    "sync-site",
    create=pulumi.Output.concat(
        "rsync -avz -e 'ssh -i ",
        config.require("ssh_private_key_path"),
        "' ",
        config.require("site_path"),
        "/* root@",
        server.ipv4_address,
        ":/var/www/",
        config.require("domain"),
        "/",
    ),
    environment={"GIT_VERSION": git_commit},
    opts=ResourceOptions(depends_on=[site_dir], replace_on_changes=["environment"]),
)

The key changes are adding an environment kwarg to the Command resource, and modifying the ResourceOptions to accept the kwarg for replace_on_changes.

With those changes, when I run pulumi up, I now get:

     Type                      Name                                      Plan        Info
     pulumi:pulumi:Stack       dev-dev                                               1 message
 +-  └─ command:local:Command  sync-site                                 replace     [diff: ]

Diagnostics:
  pulumi:pulumi:Stack (dev-dev):
    CompletedProcess(args=['git', 'rev-parse', 'HEAD'], returncode=0, stdout='8d06eff9390f2ab2701a47cb49fed8b5e8342509\n', stderr='')

Resources:
    +-1 to replace
    6 unchanged

which indicates that my sync-site resource can be replaced as desired!