Open arcticicestudio opened 5 years ago
Hi! This project looks really interesting. Did it ever get off the ground?
@wren I'm still dogfooding the existing implementation (epic/gh-33-the-way-to-go
branch) to see if it fits and works fine with an existing setup. So far I am satisfied and I think it's going in the right direction. The current blocker is just that I'm short on time to work on my all of my OSS and private projects. The Nord project continues to occupy a large part of my time, but some of the upcoming changes will (hopefully) reduce some maintenance overhead so that I can spend more time on other projects again.
Next up will be some clean ups of the current implementation and improvements I planned while building other Go projects, e.g. setting up a build system with my wand project, migrating the repository to the GitHub flow and the master
branch to main
and adapting my new repository organization for Go projects. Actual features and improvements like the YAML support and a way to use “wildcard bootstrapping“ are then on the list.
In summary, that means that the plans documented in this issue are still up-to-date and, as soon as time permits, more improvements will follow. I'll keep the entry post up-to-date and post comments here so subscribers get notified 😄
I'm also always interested in feedback from others so feel free to clone the epic/gh-33-the-way-to-go
branch and run go build -o snowsaw .
from within the repository root to build the binary. There is currently no managed installation process so the binary must be manually placed somewhere in your PATH
, but support for package managers like AUR, Homebrew and Scoop will also follow later on.
The title should hint and summarize what this document is all about: The future of the snowsaw project formed by a complete rewrite in the awesome Go language.
Even though the project is still in a very early development state with only two release versions, this rewrite is a large step forward a way more stable project foundation and better designed code base.
All implementation details and requirements are documented and tracked in the corresponding issues:
49 ⇄ #50 (⊶ 94559f40) „Initial repository clean up for Go rewrite“ — completed ✓
34 ⇄ #51 (⊶ 80935956) „MIT license“ — completed ✓
35 (⊶ 329a7549) „Git ignore and attribute pattern“ — completed ✓
46 (⊶ 162057ca) „Git mail mapping“ — completed ✓
43 (⊶ 934bdac6) „GitHub code owners“ — completed ✓
39 ⇄ #52 (⊶ 30df86d2) „GitHub Open Source Community Standards“ — completed ✓
42 ⇄ #53 (⊶ 0213c4eb) „GitHub issue and pull request templates“ — completed ✓
38 (⊶ 26a63479) „EditorConfig“ — completed ✓
47 (⊶ 7a6fdd3d) „NPM project-level configuration file“ — completed ✓
36 ⇄ #54 (⊶ acd0df4f) „remark-lint“ — completed ✓
37 ⇄ #55 (⊶ be3e59af) „Prettier“ — completed ✓
44 (⊶ 2696968e) „lint-staged“ — completed ✓
45 (⊶ 955cd7c3) „Husky“ — completed ✓
48 ⇄ #56 (⊶ 3f221b66) „git-crypt“ — completed ✓
57 (⊶ 5df9490e) „Renewed README and Assets“ — completed ✓
58 (⊶ 84f640f0) „Go Module Initialization“ — completed ✓
59 (⊶ e1286f48) „Basic level-based printer“ — completed ✓
60 ⇄ #63 (⊶ 84cf2a6d) „App config handling with JSON/YAML de/encoder and builder“ — completed ✓
61 (⊶ b1ed2cc5) „snowsaw CLI interface setup and main/root command“ — completed ✓
62 (⊶ 16221347) „Project build & development toolchain“ — completed ✓
65 (⊶ 79afc12e) „Missing "success" printer level parsing“ — completed ✓
66 (⊶ 8531f470) „Exported utility function packages“ — completed ✓
67 (⊶ 008edbcb) „Single configuration file extensions“ — completed ✓
68 (⊶ 503055c6) „Correct configuration state priority merging“ — completed ✓
69 (⊶ dea6ab56) „Migrate to YAML encoder
gopkg.in/yaml.v3
“ — completed ✓70 (⊶ 988073b1) „Snowblock API v0“ — completed ✓
71 (⊶ f68ec432) „
snowblock.TaskRegistry
API implementation“ — completed ✓72 (⊶ efdff96e) „
snowblock.Snowblock
API implementation“ — completed ✓73 (⊶ 145a4c36) „
bootstrap
base command“ — completed ✓74 (⊶ 4121393e) „
Link
task runner API implementation“ — completed ✓75 (⊶ c511fa1f) „
Clean
task runner API implementation“ — completed ✓76 (⊶ 006ae998) „Multiple tasks of same type only handled once per snowblock configuration file“ — completed ✓
77 (⊶ ed6226d9) „Global
basedirs
flag uses wrong default value“ — completed ✓78 (⊶ 9366c4a9) „Pass individual snowblock paths as arguments instead of
--snowblocks
/-s
flag“ — completed ✓79 (⊶ a78810b7) „
Shell
task runner API implementation“ — completed ✓80 (⊶ 81369056) „Handle problems detected by used linters“ — completed ✓
81 (⊶ b7de6dbf) „Switch from npm to Yarn“ — completed ✓
82 ⇄ #85 (⊶ 278d08e5) „Prevent
go.mod
file pollution with development dependencies“ — completed ✓83 ⇄ #84 (⊶ 3bb0b897) „Drop cross-compilation of FreeBSD binary artifacts“ — completed ✓
86 ⇄ #87 (⊶ 7fb34caa) „Update to Go 1.13 and latest dependency versions“ — completed ✓
88 ⇄ #89 (⊶ 213111d3) „Development dependency global installation workaround“ — completed ✓
90 ⇄ #91 (⊶ ad911918) „Global tool/dependency managing with
gobin
“ — completed ✓92 ⇄ #93 (⊶ 532e6800) „Assemble app version with pure Go Git and SemVer libraries“ — completed ✓
94 ⇄ #95 (⊶ 64a432e2) „Refactor
info
command and rename toversion
“ — completed ✓To test the current development state or keep track of the completed tickets check out the epic/gh-33-the-way-to-go branch. See the linked ticket above and the development workflow section below for more details.
Please report every bug to help making the project more stable. Every feedback is always welcome! :muscle:
A Small Excerpt From The Project History
The origin of the project is a port of the great Dotbot. I've searched for a tool to manage my .dotfiles and found long-time and stable projects like GNU Stow, Ansible or homesick as well as many more through great resources like GitHub's official .dotfiles website and awesome lists like awesome-dotfiles, but unfortunately none of them could fulfil all my requirements:
add
,update
orcommit
that are nothing else than wrapper around the Gitadd
/commit
core commands. Such features only add unnecessary complexity to the tool, reducing the transparency of what is really happening “under the hood“ and destroying the purpose of the UNIX philosophy (“Do One Thing and Do It Well“) as well as the KISS (“keep it simple stupid“) and DRY (“Don't repeat yourself“) principles. The only reason for such features might be that users don't need to know some simple Git basics (or Git at all), but if you're creating and tracking .dofiles the chance that you're not familiar with Git is close to zero. If you're modifying your .dotfiles in any way, Git provides you with all necessary tools and even if you're new to Git there are fantastic resources like Atlassian's Git guides and documentations that'll teach you the basics within several hours.Based on these requirements I tested a lot of the existing tools and the ones that matched the most were Ansible and Dotbot, but unfortunately both also couldn't fulfil the requirements of being portable. Ansible can convince with large ecosystem, a granular configurability and the usage and extensibility with modules, but also comes with a lot of overhead for small projects like a .dotfile repository. It is mainly targeted for the commercial administration of large, distributed systems and the setup is way too over engineered for such a use case. Dotbot also provides flexible configuration features and can also convince through it's modularized design by using dedicated plugins for tasks like linking, copying or execution of commands through a shell process, but there were also features missing that were a must-have for me. The day after the evaluation was the birth of snowsaw.
Previous Design Decisions
Even though Dotbot is written in Python, that pulls in the dependency to the Python 3 interpreter and runtime, I've decided to base snowsaw on it. The decision was quite easy because to the time I've evaluated existing tools there was no stable and reliable project that was written in a portable language like Go, C/C++, Rust or anything else that (statically) compiles into a single (binary) artifact and also fulfils most of my requirements listed above. In comparison to other similar projects like Dotbot that are written in Python, there is only one external Python library dependency next to the Python 2/3 runtime itself. It is not essential and only adds support to optionally write configuration files in YAML instead of only using JSON, but this is not a must-have requirement for snowsaw.
The facts described above lead to the decision to port Dotbot and implement the missing features. Even though I'm a long-time Linux user and have some experience in Python (wrote some scripts where a shell script might be too complicated), I don't like script languages at all and always prefer type-safe compile-time languages like Go, Rust or Java. The only exception is JavaScript when used for websites or Electron/Web apps with React which is in my opinion the best way to build a UI since web technologies like CSS were invented for it. Next to this, Python also comes with the Python 2 to Python 3 ecosystem split-up and a likely broken package management, global vs. local package installations with
pip
(that also has a slightly complicated installation process itself) that can cause problems with native OS package managers (apt
,yum
,pacman
etc.) becausepip
bypasses their tracking logic.However, since Dotbot provides most of my desired features I decided to stay with Python.
snowsaw Goes Its Way
Like described in the project history above, the only reason to use Python was because of its Dotbot origins. During the development of more (requested) features and the fixing of bugs I often faced some problems everybody faces when writing in a language in which one is not so experienced. I always see such problems as opportunities to learn more about something new and gain experience, but after a while I unfortunately lost the interest iun Python for many reasons also described in the previous sections above.
In the meantime I expanded my knowledge in Go and until today I get more and more into love with this awesome language with each line of code. Some days ago I decided to take a few days off from porting all of Nord's port project to the shiny new website and wandering through my currently over 620 (!!!) notifications about open issues and PRs which are scattered in all my projects and other contributed repositories. After landing at snowsaw and trying to wrap my head around some of the pending tasks and how to solve them (with Python skills that are already dusted again :smile:), I had the lightning thought (and wish) that it would be awesome if snowsaw would be written in my favorite language: Go. And that's the reason I'm currently writing this wall of text :smile:
What To Expect
Before rewriting and reviving the project from its kind of „hyper sleep“ I want to make the process clear to all snowsaw users. Even though this started as a project for my personal use, it got some more attention and quite and larger user base. This means simply implementing everything and pushing it to the
develop
andmaster
branches with a new version will break many users expectations and maybe their .dotfile setup too.In order to carry out the project rewrite I want to clarify some general aspects and details:
snowblock.json
configuration files means all existing setups will break if the changes are not adapted manually. There will be changes to the schema, but they will be handled through a schema version similar to theversion
field of docker-compose. This will help to differentiate between “legacy“ configurations and new ones, allowing to use a new Go language based snowsaw version with “legacy“ configurations. It will be made possible through a specialized handler that convert these configurations internally.<1.0.0
! As of v1.0.0 all code related to legacy support will be removed in order to achieve a clean and maintainable code base. For users who like to stay with a legacy snowsaw version, every version<1.0.0
will be suitable while the current Python-based snowsaw can also be used.develop
branch and released inmaster
through a new tagged version, the support for the Python based implementation will be dropped in aspects like feature requests, bug fixes or support/questions regarding the setup. This might sound a bit drastic, but my free time is really limited and the time I spend for the open source community shoots far beyond a normal volume (even though I will always enjoy every second of if :green_heart:) so I can't effort to support code that only exists in the Git repository history anymore.Bye Bye Loose Plugin Architecture
One of the larger features of snowsaw was the plugin architecture that allows extend snowsaw's functionality by dropping a Python script into the
plugins
directory in order to let snowsaw handle other tasks defined in any snowblock configuration file. By default snowsaw came with the three core pluginsclean
,link
andshell
to provide basic and most of the time completely sufficient tasks to handle almost everything needed to manage .dotfiles. As far as I can tell (information only based on public repositories on GitHub!) most users of snowsaw never used custom plugins since the bundled ones served all necessary functions. This is a more or less relevant information since this means the omission of this feature for the new Go implementation will have almost no impact on the usability. Adding a new plugin to handle other tasks was possible by satisfying thesnowsaw.Plugin
interface that requires the plugin to implement thecan_handle()
andhandle
methods. This more or less unstable pattern is the reason why this section's headline uses the „loose“ plugin architecture wording since Python is not designed for type safety as well as concepts like strict interface implementations. snowsaw was instructed to assume that the plugin author has read the documentations regarding the required behavior and return values of these functions.Luckily Go is a type safe language and it's language design makes heavy use of interfaces that require correct implementations, but due to it's nature of being a compilation language it is not that easy to introduce a plugin system. I've spend a lot of time to think about a way to keep the previous plugin-driven architecture up for the rewrite and evaluated the following possible solutions:
Go Standard Library
plugin
PackageGo comes with the
plugin
package by default that allows to load and resolve symbols of other Go artifacts, a so called „Go plugin“. It allows to load the files from anywhere on the same filesystem and make use of any exported type or function. It was first introduced in Go 1.8 and at the time sounded like the perfect solution to build modular and dynamic applications with endless expandability. Anyway, one downside was the restriction to be only compatible with Linux. Later on, Go 1.11 added support for macOS and support for Windows is on it's way. A Go plugin can be easily compiled by simply usinggo build
with the specific-buildmode=plugin
flag in order to compile the target packages to a.so
file. There are also more supported build modes, e.g. to create a shared library that can be imported into any other language like C or Python (buildmode=shared
orbuildmode=c-shared
) or also to create position independent executables (PIE) through the-buildmode=pie
flag. Anyway, I don't want to go into details here, but if you want to take a deep dive into this topic please take a look at the officialplugin
package documentations,go help buildmode
andgo help build
as well as many other references and tutorials out there.As beautiful as that sounds, there are also several difficulties when using Go plugins making it too hard to maintain and develop for such a small project like snowsaw. This is not the marching solution to let users add in their own code, they need to adhere to many rules, configurations/setups and conventions when building a custom Go plugin due to the following points:
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go list -deps -f='{{if not .Standard}}{{.Module}}{{end}}' <APPLICATION>
, but plugin authors will need to also pin those versions (go.mod
,Gopkg.toml
etc.)GOPATH
setting as the application (even if using modules). This means when using CircleCI as CI/CD service, users who use plugins must setGOPATH=/home/circleci/go
, even though they don't have acircleci
user.libc6-compat
, everything must be compiled for compatibility with LSB 3. Setting_FORTIFY_SOURCE=2
with GNU libc causes the CGO 1.12 runtime to require LSB 4. Several distributions (including Ubuntu 14.04 used by CircleCI) patch their GCC to define_FORTIFY_SOURCE=2
by default. When compiling plugins, users may need to fuss with settingCGO_CPPFLAGS
to make things not fall over.libc
dynamic linker, they forceCGO_ENABLED
on sp cross-compiling is no longer easy to do. Someone wanting to compile a plugin for the GNU/Linux program binary from their macOS workstation must compile the plugin in Docker or any other VM.Hopefully all these bullet points will be obsolete later on when the
plugin
packages gets improved with future Go versions, but in the meantime this is not the desired solution.There are other plugin systems designs out there, e.g. by using Go's
net/rpc
package that allows the main application to communicate with plugins through remote procedure calls. A more advanced solution is the awesome go-plugin project by Hashicorp that brings all these functionalities out-of-the-box with a easy-to-use API, many additional feature and also full support to use the awesome gRPC project instead of the more basic (and limited)net/rpc
package. They're using their own package in famous and busniess-critical projects like Terraform and Vault and I've also used it in some other private/public/dayjob projects. It's performance can not be compared to native Go plugins, but even in production with really heavy throughput there is no noticeable problem or bottleneck. Anyway, even though a gRPC based solution for plugins for snowsaw would work really well, it is too over engineered and only brings in unnecessary complexity for such a small project that aims to lightweight and tries to follow the KISS princicle and Unix philosophy.These are some facts which must be considered when snowsaw would use Go plugins and these are also all reasons why snowsaw won't adapt to this concept. For more details, please read the official Go
plugin
package documentations, join the official Gophers Slack workspace and take a look at posts like this in the official /r/golang subreddit.Long story short: The initial Go implementation of snowsaw won't use a plugin architecture anymore, but will come with necessary functionalities out-of-the-box to handle almost every use case for dotfile management. There will be a kind of „task“ API with interfaces that'll be implemented by snowsaw's core features and it will be exposed as exported types, allowing users to implement custom task handlers to extend snowsaw's capabilities. Later on a detailed documentation will be added plus resources to simplify the process of compiling the project together with custom task handlers, e.g. a Dockerfile that can be used to automatically place custom code in the correct package folder, build the project and copy the resulting artifact from the container to the host while leaving the host system in a clean state without the requirement to even clone and set up snowsaw's repository.
Next Steps
This document will serve as the epic issue and keeps track of all the sub-tickets that are listed at the top below the introduction paragraph. Before starting the actual implementation I will create the design concept tickets that'll be used to build the repository, documentation and code base from scratch. Note that this might take some time since it is not a high priority task and will be done step-by-step when there is some time left from the more urgent tasks like the Nord port project data transitions.
Development Workflow
Since this issue represents the main epic there will be a branch all results of the sub-tickets and stories will be merged into. As soon as everything is finally completed this branch will be merged into the main
develop
branch and later on intomaster
to create a new version tag and deploy it. This way the rewrite can live together in parallel with the current code base without leaving it in an unusable state.Build With & For The Community
Even though snowsaw was mainly developed for my personal use cases it is a open source project that means everyone can contribute to push the project forward and help to form its future.
If you like to test the new rewrite or keep track of the actual development state you can check out the epic/gh-33-the-way-to-go branch and follow the design concept documents and linked implementation ticket listed above.
Please report every bug to help making the project more stable. Every feedback is always welcome! :muscle: