Hacker News new | ask | show | jobs
by kjksf 817 days ago
I do that in my Go projects.

In fact my "scripts" are actually part of the main executable. I use cmd-line args to invoke the needed functionality.

For example, in the past I would have written a Python script to deploy my Go binary to a server, possibly using tools like Fabric that provide functionality to make it easier.

Today I add `-deploy-hetzner` cmd-line to my Go binary and it does the work. It builds itself, copies the binary to the server, kills the old instances, configures caddy if needed, starts newly uploaded instance etc.

For example my deploy.go is 409 lines of code, which is not that bad. You can see exactly how this works: https://github.com/kjk/edna/blob/main/server/deploy.go

I standardized on how I deploy things so deploy.go is mostly re-used among several projects.

Writing this code isn't much more difficult that what I used to write in Python.

This kind of code can be shorter because I don't have to handle errors, I just panic if something goes wrong.

I like that I don't have to switch between different languages and that I have full control and understanding over what happens. Fabric used to be a bit of a black box.

I even wrote an article about this idea: https://blog.kowalczyk.info/article/4b1f9201181340099b698246...

5 comments

Im a big fan of //go:build exclude ... and having separate CLI scripts in a project where I can.

Candidly I think that it's much easier to do this in go (or rust), rather than say python/ruby/node as I can use complied binaries without needing a run time.

Edit: //go:build ignore is idiomatic

Go’s excellent for writing development tools and “scripts” if you have to support development on heterogenous platforms.

No per-platform install instructions, no “ok but you have to run this in WSL2… you don’t have that? Oh god, ok, let’s find those instructions…”, no “oh fuck the flags for that are different on BSD and Linux, so this breaks on macOS”. “Wait the command’s python3 not python, on your system? And also it’s erroring even after you fix that? Shit you’re still on 3.8, we use features that weren’t included until 3.10…”

“Unzip this single binary and run it. Tell your OS to trust it if it hassles you. That’s it.”

C# with mono might be close to as good? Not sure. Rust’s probably OK but a bit of an investment when you’re just trying to dash off some quick tool or script. C++ and C can do it but you definitely don’t want to. Java’s obviously out (ok, now update your JRE… wait, your env vars are all fucked up, hold on…).

Go’s also highly likely to be something another developer with or without Go experience can read and tweak after the person who wrote it leaves, without much trouble.

Why use build tags for this over ./cmd/$toolname/main.go?
Do build-excluded files get tested with `go test`? (tbf, not sure it's practical to test most scripty tasks)
Think of this as a way to have a file with package main, and a func main that does not interfere with your normal build process...

The best example of this, and a decent util is this: https://go.dev/src/crypto/tls/generate_cert.go

All of the real testing happens elsewhere this just provides utility.

This seems like a terrible case of not separating concerns and needless optimization. It may work for you, but what if someone who does not know Go but wants to use your project on a provider that is not hetzner?
> It may work for you, but what if someone who does not know Go but wants to use your project on a provider that is not hetzner?

Well they'd run into the same problem if the script was Python, wouldn't they?

And Go is ultimately far far easier to read, modify[1] and debug for someone who doesn't know it than Python is.

[1] For example, adding a third-party library to handle new cases. I've never had a good time doing it with Python's various package management, but Go seems pretty good at it.

> Well they'd run into the same problem if the script was Python, wouldn't they?

No, they would just use whatever tool is more appropriate for the job.

Fabric would be fine, ansible would be fine, salt would be fine, putting it on Docker and deploying via compose would be fine. None of these tools force you to learn the implementation details of the application.

> Fabric would be fine, ansible would be fine, salt would be fine, putting it on Docker and deploying via compose would be fine.

That still leaves them the problem of "If I don't know this tech stack, what do I do?"

You are working on the assumption that everyone already knows Fabric, Ansible, salt or Docker.

Whichever poison you choose, people who don't know that particular poison won't know how to install to a different provider anyway!

You're making an argument against one particular poison, but I don't see it as any better or any worse than the others, which all have enough complexities to fill a book.

> "If I don't know this tech stack, what do I do?"

If you don't know how to use a tool that does one job, either you learn it or you just delegate that job to someone who knows it already. But defending the practice of having your application code also responsible in dealing with deployment and packaging smells a lot of "coders are gonna code".

> If you don't know how to use a tool that does one job, either you learn it or you just delegate that job to someone who knows it already.

How is that different from "If you don't know how to use the tool (Go), either you learn it or you just delegate that job to someone who knows it."?

> But defending the practice

Woah there cowboy, I wasn't advocating an architectural decision suitable for a FAANG. I was "defending" the position of "why you an extra tool for this one program that has no other dependencies?"

> of having your application code also responsible in dealing with deployment and packaging smells a lot of "coders are gonna code".

In much the same vein, switching to Ansible, Fabric, etc simply to install a single binary to a single place smells a lot like "resume-driven-development".

You are being needlessly picky here, Golang is in fact a very viable replacement for both sh/bash/zsh and Python scripts, it compiles to a single static binary, can be cross-compiled to every supported OS on another host, and is thus easy to distribute.

I already started replacing parts of my shell scripts with Golang programs and the experience is miles ahead.

And if the project is already written in Golang, it makes even more sense if the dev is also partially the DevOps. Why wouldn't they make the experience as seamless as possible for themselves and the rest of the dev team? What good would Ansible do if everyone has to yet learn it?

As for the person you originally replied to, I'd definitely make a separate program that provides tooling related to the main program... but that's the only different thing I'd do.

Your snarky "coders gonna code" comment is detached from reality, we have jobs to do, and within deadlines.

Well, maybe he never wants "someone" to deploy his project. I think it is perfectly legitimate to handle it this way for a one-man hobby project.

For a project where multiple people are involved, I think it's better to split concerns. Deployment should be handled transparently and reproducible in a CI pipeline that the team members who are allowed to deploy have access to.

But if you are a one-man show (and you want to keep it that way) you are in no condition to tell what would be the best practice for a team.
Yes you are. You are telling yourself what's the best way to do things in your own team, that is comprised of only yourself.
If your argument can only be made in a very specific set of conditions and only applies to yourself, it's just a preference.

I don't care how you prefer to do things on your own, but if I were interviewing you for a senior position in a team with a handful of people, and you tell me that's how you want them to work, it would be an almost immediate NO HIRE.

Yes, but you are moving goalposts. I was under the impression we were talking about the one-man army case only, not how they conduct themselves on interviews which is a VERY different topic.
Not OP, but I presume that one could add `-deploy-aws` cmd-line to the Go binary with whatever steps AWS requires.
Yeah, this is the way to end up with ad-hoc, bug-ridden, half-baked implementation of fabric (or ansible, salt, helm, nix, or any tool that is more appropriate for this job)

And you haven't addressed the issue that whoever is performing the role of DevOps on this project must know Go.

If your person doing DevOps can't learn GoLang, you have bigger problems.
Nice "No true Scotsman", and you are completely missing the point.
You are erroneously invoking a fallacy that does not apply here. A qualified DevOps engineer will be able to pick up GoLang. I didn't claim that a DevOps engineer already knows GoLang, but that they're able to pick it up. If I'd say only true devops engineer's already know golang, you'd have a point.

Unfortunately, you missed it too.

Do fabric,ansible, salt, helm, or nix have the functionality of deploying to cloud infrastructures? There is a lot more to deploying to cloud infrastructures then just provisioning a server.

I've never seen these tools used in this capacity, especially nix.

I think such things have existed, but Nix deployment tools being relatively small projects with small teams or solo devs, a more common approach is to piggyback off of other IaC tools provisioning cloud infrastructure, e.g. by templating Terraform in Nix¹.

From a Nix user's perspective, this leaves a lot to be desired, because tools like Terraform are much less reliable than Nix tools that target local machines, and their management of, say, a VPC, is much less comprehensive than NixOS' management of an individual operating system. And they're slow as hell. But for the most part these issues are inherited from the APIs cloud providers expose, which don't meaningfully or uniformly support immutability.

--

1: https://github.com/terranix/terranix

One could argue that if you are at the stage of "provisioning a server", then you should be using something like Terraform or Pulumi, not configuration tools like the one I mentioned.

Anyway, for the specific case of "deploying to hetzner":

- ansible has a whole collection of modules for hetzner: https://docs.ansible.com/ansible/latest/collections/hetzner/...

- For Nix, there is NixOps

- if no one in the team wants to learn any of that, Hetzner provides a CLI to interact with their whole cloud.

To repeat: the last thing I'd want from an application is to have its own deployment system.

I tend to use go for "scripts" in non-go repos (typescript, C, etc) just for the simplicity of "you only need to install go" and pretty much all functionality is included in the standard library (usually something like JSON/XML/CSV parsing).

I'm OK with python for scripts, but go "just works".

Python can be annoying for scripts because they are often hard to run on another machine due to the dependency story.

I think Go scripts have a much better odd of “just working”with only the Go toolchain installed.

Go feels especially suited for moving between application code and command line tools.
I do this as well, but I pass two main arguments to my main executable task, and args. For example:

go run cmd/server/main.go --task db --args migrate

go run cmd/server/main.go --task cron --args reset-trial-accounts

etc.

I use https://github.com/spf13/cobra religiously for this kind of thing - it handles all the annoying corner cases of parsing flags, and also has an intuitive notion of subcommands (with basic usage/help text generated) for picking which task you want to run with positional arguments.
I'll have to mess with it and see. It looks like it could make the general CLI ux better :)