Rather than a suggestion, I'll simply share what I ended up doing with the platform I was tasked with writing an operator for. The platform had very similar structure and requirements as what you are looking to do. I decided to go the golang operator route as it was a good time to get a deeper understanding of golang, and I wanted to have a good deal of control over the process.
At first, I chose to convert every helm template object into the equivalent golang object and create them. Early on we learned that we would want to continue to iterate using helm templates as we made changes. Rather than continue to keep converting and maintaining the golang object structures for each helm template item, we decided to use the helm rendering engine to generate all of the manifest files, which we would then go on to use to create the individual objects. The rendered templates can be pulled into golang objects using "
k8s.io/apimachinery/pkg/util/yaml" NewYamlOrJSONDecoder.Decode.
renderedYAML := <yaml for a secret>
resource := &corev1.Secret{}
if err := k8sYaml.NewYAMLOrJSONDecoder(strings.NewReader(templateString), 100).Decode(&resource); err != nil {
Then the "resource" can be used in r.Client.Get (using .Name and .Namespace for instance), r.Client.Create, etc.
For us this ended up being a good approach, as we still publish our helm chart and our platform can be deployed with just helm, and no operator. This is great for some internal testing, and also for ease of end user initial setup for evaluation.
The process of prepping the map[string]interface{} structure to hold the equivalent of the a single top level values.yaml file was not trivial, at least not for the level of golang experience I had at the time. Eventually it ends up being a merge of the incoming CR overtop a generated map from all of the dependency chart values.yaml files.
Each dependency chart values.yaml file is read into a map[chartname]interface{}, the incoming CR may have values defined that override sub-chart values, so it is merged on top of the base values.yaml structure. Globals also come in with the CR and must be injected into each map[chartname] level, and sometimes lower depending on additional sub-charts of your sub-charts.
Once you have that, you pass everything into the helm render engine, and it gives you back a map[string <template file path>]string <the rendered template>.
From there you process the items in the order you want to create them in. We have pretty good luck with it. Our top level chart values.yaml file is used to auto-generate our base CR file, new entries there require definition in the operator structs, and new template items need code added to create those items.
Hopefully you'll get some other shared experiences to help guide your development path.
~cj