Closed aledbf closed 3 years ago
So this is going to be a long answer, because this is a good question. 🎉
I am going to do my best to offer blanket statements that can be referenced, and just capture the state of ecosystem. I have a few opinions in here, and I will do my best to call out my opinions from the facts. Ultimately my goal is to be kind and offer a good resource.
Basically it would be some sort of system in Go that you and your team will write.
In my opinion, the reason that Values.yaml
exists in Helm is because it offers an abstraction and a means to manage the abstraction.
This is convenient because it allows authors to "stich" or "glue" various configuration together to create the concept of a holistic application configuration.
.
├── Abstraction # <-- Helm calls this Values.yaml
└── KubernetesObjects # <-- Helm calls this "templates"
├── ConfigMap # <-- These are Kubernetes objects
├── Deployment # <-- Helm calls this deployment.yaml
├── RoleBasedAccessControl # <-- More Kubernetes objects
└── Service # <-- More Kubernetes objects
The "Abstraction" pattern overall is a useful idea. However for some users the .yaml
implementation does a bad job at offering traditional features of programming languages.
In other words Helm's Values.yaml
is not only just the concept of a higher level abstraction, it also is a prescription on how to abstract the Kubernetes objects.
It is both an abstraction pattern, as well as the implicit tooling (helm) to manage the abstraction.
To be honest, (my opinion) I don't think it's a very good abstraction pattern, especially when you look at managing large archives of applications, testing, and standardizing applications.
This is a hard problem to solve, and Helm took a swing at trying to offer an abstraction as well as a Turing complete configuration paradigm on top. Linux also ran into this problem with package management with .deb
, .rpm
, and .pkg.tar.zst
packages.
That is to say the packages started out just being a static 1:1 mapping of files and a location on the filesystem. Over time more and more Turing complete logic crept into these packages. Now all of the major Linux operating systems have enabled package authors to do basically whatever they want at runtime when it comes time to installing packages.
pkg.tar.zst
files are usually just "deflated", however we all know that plenty of Arch packages break this paradigm with start
and stop
scripts.
The lesson here is that static application packaging sounds nice on paper but just don't fucking work in the real world.
If()
statements and Range()
loops in the .yaml
files.apt-get
can produce different results on different systems.pacman -S
will install package_x86
or package_arm64
based on the kernel.curl | bash
instead of offering a .deb
or .rpm
brew install
can install ruby .rb
files with if statementsThis project (naml) for one embraces our new Turing complete package management overlords, and it embraces it with raw Go.
Meaning that in a world where abstractions used to ship with tooling on how to manage the abstraction (EG: helm) we are doing something different. We are offering a (more traditional) approach at managing applications.
I put a lot of thought into the interface, and ultimately the interface is the new constraint for managing your abstraction.
With all that said I can now respond to your original issue
The full example uses arguments but, in some scenarios, is not practical. For instance, take this old chart for a docker registry
The paradigm here is that a software engineer would need to be responsible for implementing Install()
. Which means we would expect a developer to design, build, and implement their own systems here. This is a bold fucking statement, and I know it is.
So if you had a lot of configuration in your abstraction like in the link you shared. You could do something like this.
type MySampleAppPublic struct {
ExampleValue string
ExampleNumber int
ExampleText string
ExampleToggle bool
ExampleVerbose int
ExampleName string
ExampleAnnotations map[string]string
ExampleValues map[int]string
ExampleValue1 string
ExampleValue2 string
ExampleValue3 string
}
With corresponding calling code such as
func main(){
// ...
publicApp := &app.MySampleAppPublic{
ExampleValue: "",
ExampleNumber: 0,
ExampleText: "",
ExampleToggle: false,
ExampleVerbose: 0,
ExampleName: "",
ExampleAnnotations: nil,
ExampleValues: nil,
ExampleValue1: "",
ExampleValue2: "",
ExampleValue3: "",
}
// Pass the public app fields to the New() function
a := app.New("my-name", "my-namespace", "my-description", publicApp)
//...
In a perfect world the ultimate design of these systems are up to you and your team.
There are trade offs to how you expose the fields, and considerations about things like
Again -- the entire point of naml is for enterprise application managers who have enough to manage that writing this stuff in code makes sense. If they are at the point of writing code, I am assuming they are at the point where they can craft their own systems to do things like this.
Feel free to re-open this issue if you have any more questions.
Thanks for asking a great question, and good luck helping us all burn down late stage capitalism.
@kris-nova thank you for taking the time to reply with this level of detail :clap:
To be honest, (my opinion) I don't think it's a very good abstraction pattern, especially when you look at managing large archives of applications, testing, and standardizing applications.
Agree. Right now I need to migrate an existing chart with a bazillion options, and no tests :upside_down_face:
The
full
example uses arguments but, in some scenarios, is not practical. For instance, take this old chart for a docker registry