Setting variable from file content in dockerize golang template - go

I want to use the jsonQuery syntax in dockerize to parse traefik's acme.json and emit cert/key files for TLS settings in another service.
jsonQuery accepts a string, which the example gives as an environment variable {{ .Env.myJson }}
How might I get the string contents of a file:
{{with $myJsonContent := <insert magic here> }}
# extract key to file
{{end}}

Go's text/template doesn't natively support that. It looks like the dockerize tool provides a couple of extension functions but none of them allow this either.
(The nearest thing I can think of is that kubernetes-helm supports reading a file from a Helm chart, but that's implemented at the Go level by injecting a special accessor object that can provides the file-access API to template code, and it's intentionally limited to files physically located within the Helm chart directory.)

I can't see any way to add a function to the template, as dockerize doesn't expose the addition of functions to the template prior to parsing. So you'll either have to (1) get the contents of acme.json into an environment variable, or (2) modify dockerize to include a jsonFileQuery function in the templates.
Add the contents of acme.json to the environment variables before running dockerize - then access as in the example. This could be done with a small go program, added to the container and run via CMD prior to the CMD dockerize
Fork dockerize and change jsonQuery: Fork dockerize and change line 83 of template.go:
from: parser, err := gojq.NewStringQuery(jsonObj)
to: parser, err := gojq.NewFileQuery(jsonObj)
Then use:
{{with $myJsonContent := jsonQuery "/opt/traefik/acme.json" "toplevelobject" }}
# extract key to file
{{end}}
gojq.NewStringQuery() is a function behind the jsonQuery template function. The gojq.NewFileQuery() version has the same signature as the StringQuery but reads the file at the path in the input string instead of using the input string as json.
OR
Merge new jsonFileQuery template function into dockerize: submit an issue to dockerize to add jsonFileQuery to the template functions. Seems like it could be set up the same as jsonQuery but with the small difference above. In template.go, add the jsonFileQuery function and assign it to jsonFileQuery in the template.FuncMap{} within generateFile().

Related

Question about weird behavior referencing a YAML pipeline resource using a variable for the pipeline resource name

I am experiencing weird behavior with YAML variables, parameters, and Azure pipeline resource references. The following shows the original implementation that works compared to my new implementation with a single line change that fails.
Working Implementation
Template A (makes a call to template B):
- template: Templates\TemplateB.yml
serviceBuildResourceName: resourceName
Template B (uses serviceBuildResourceName param to get pipeline run information):
$projectId = '$(resources.pipeline.${{ parameters.serviceBuildResourceName }}.projectID)'
$pipelineId ='$(resources.pipeline.${{ parameters.serviceBuildResourceName }}.PipelineID)'
Template B goes on to use the values in $projectId and $pipelineId (along with other values not listed here since it is irrelevant) to successfully retrieve information about the a pipeline run from the specific pipeline resource, serviceBuildResourceName. Note that all pipeline resources are correctly defined at the beginning yaml file for the pipeline. In this implementation above, everything works perfectly.
Failing Implementation
Template A (makes a call to template B):
- template: Templates\TemplateB.yml
serviceBuildResourceName: $(ServiceBuildResourceName)
Template B (uses serviceBuildResourceName param to get pipeline run information):
$projectId = '$(resources.pipeline.${{ parameters.serviceBuildResourceName }}.projectID)'
$pipelineId ='$(resources.pipeline.${{ parameters.serviceBuildResourceName }}.PipelineID)'
Note that the only difference is the following: instead of passing the hard-coded string into the serviceBuildResourceName parameter, I pass in a variable, which has the same value as before, resourceName. The variable is defined in an earlier template as such:
- name: ServiceBuildResourceName
value: resourceName
I feel it should still work the same, but I know get the following error in my pipeline run:
WARNING: 2023-02-12 15:52:29.5071 Response body: {"$id":"1","innerException":null,"message":"The value is not an integer.
$(resources.pipeline.resourceName.PipelineID)
I know that the variable is being correctly populated since the error message above contains "resourceName" in resources.pipeline.resourceName.PipelineID, as it should.
However, for reasons unknown to me, it now throughs an error. It seems like it doesn't recognize the pipeline resource, and instead recognizes it as a string.
Any help or insight here would be greatly appreciated, thanks!
As far as I can tell, this is because of how predefined variables work in YAML. Since resources.pipeline... is a predefined variable, it gets resolved at compile time. Thus, you can't use run-time defined variables like I am doing. Instead of resolving it as a predefined variable, it will get resolved to be a string at runtime.

i18n support for Golang Web application HTML templates

Does anyone has a good idea to localize HTML templates in Golang Web application? Now I'm using Gin and go-i18n, but I will use other frameworks if they can localize.
If possible, I want to define the localized messages in property (or JSON/yaml/toml,...) files for each language:
label.password = パスワード # Password in Japanese
and write localized html like Thymeleaf:
<label th:text="#{label.password}"></label>
I chose to use i18next instead of go-i18n because the message keys can be embedded in HTML and replaced with language JSON files.
Spreak supports template localization by passing a localizer as template data.
It can also automatically extract the strings to be translated.
The process is similar to that of go-i18n.
Create your templates:
<label>{{.T.Get "Password"}}</label>
Load the translations:
bundle, err := spreak.NewBundle(
spreak.WithSourceLanguage(language.English),
// Set the path from which the translations should be loaded
spreak.WithDomainPath(spreak.NoDomain, "locale"),
// Specify the languages you want to load
spreak.WithLanguage(language.Japanese, language.German),
)
Create a localizer for a request and pass it as template data.
func(c *gin.Context) {
accept := c.GetHeader("Accept-Language")
localizer := spreak.NewLocalizer(bundle, accept)
c.HTML(http.StatusOK, "template.html", gin.H{
"T": localizer,
})
}
Use xspreak to extract the strings to be translated.
go install github.com/vorlif/xspreak#latest
xspreak -D path/to/code -o path/to/code/locale/base.pot --template-prefix "T" -t "templates/*.html"
A directory locale with a file base.pot is created.
The file is the template for new translations and contains the strings to be translated in po-format. The structure of the file follows the structure:
#: ../template.html:8
#, go-template
msgid "Password"
msgstr ""
For editing, it is best to use an editor like PoEdit, but there are also many good alternatives and online platforms.
Open the file base.pot with an editor and create your .po files with the translations.
In your example, this would be the file locale/ja.po with the structure
#: ../template.html:8
#, go-template
msgid "Password"
msgstr "パスワード"
Start the application and the matching translations will be used. The original text will be displayed if there are no matching translations.
I also created a full example using .i18n.Tr as translation method in the templates and using multiple languages.
Note: I am the author of spreak.

How to use kubebuilder's client.List method?

I'm working on a custom controller for a custom resource using kubebuilder (version 1.0.8). I have a scenario where I need to get a list of all the instances of my custom resource so I can sync up with an external database.
All the examples I've seen for kubernetes controllers use either client-go or just call the api server directly over http. However, kubebuilder has also given me this client.Client object to get and list resources. So I'm trying to use that.
After creating a client instance by using the passed in Manager instance (i.e. do mgr.GetClient()), I then tried to write some code to get the list of all the Environment resources I created.
func syncClusterWithDatabase(c client.Client, db *dynamodb.DynamoDB) {
// Sync environments
// Step 1 - read all the environments the cluster knows about
clusterEnvironments := &cdsv1alpha1.EnvironmentList{}
c.List(context.Background(), /* what do I put here? */, clusterEnvironments)
}
The example in the documentation for the List method shows:
c.List(context.Background, &result);
which doesn't even compile.
I saw a few method in the client package to limit the search to particular labels, or for a specific field with a specific value, but nothing to limit the result to a specific resource kind.
Is there a way to do this via the Client object? Should I do something else entirely?
So figured it out - the answer is to pass nil for the second parameter. The type of the output pointer determines which sort of resource it actually retrieves.
According to the latest documentation, the List method is defined as follows,
List(ctx context.Context, list ObjectList, opts ...ListOption) error
If the List method you are calling has the same definition as above, your code should compile. As it has variadic options to set the namespace and field match, the mandatory arguments are Context and objectList.
Ref: KubeBuilder Book

Golang templates won't load

I started to write a Gin application and my project tree looks like
-assets
--css
---{bootstrap}
-templates
--layouts
---footer.html
---head.html
---header.html
--book.html
-main.go
In main.go I load templates and there is no error
router.LoadHTMLGlob("./templates/layouts/*.html")
I define templates
{{ define "head" }}
<head>
//Head
</head>
{{ end }}
And I nest them
{{ define "header" }}
{{ template "head.html" . }}
//HTML
{{ end }}
But when I try to use them, I get empty output
{{ template "header" . }}
<h1>{{ .Title}}</h1>
<h3>{{ .Author.Fullname}}</h3>
[Edit] Function that executes the template:
func getBook(c *gin.Context) {
//DB stuff
var book models.Book
t, err := template.ParseFiles("templates/book.html")
if err != nil {
log.Println(err)
}
t.Execute(c.Writer, book)
}
Full-code can be found on github
router.LoadHTMLGlob and template.ParseFiles are two separate approaches to deal with templates. The template returned by ParseFiles has no knowledge of the templates loaded by LoadHTMLGlob. Once you decide to use LoadHTMLGlob you should then use c.HTML to render your templates. And the name argument to this c.HTML method would be either the name specified in a {{define "name"}} action or the base name of the template file (including the extention I believe).
So in your case you should probably do something like this:
c.HTML(http.StatusOK, "book.html", book)
More examples can be found here: https://gin-gonic.com/docs/examples/html-rendering/
Keep in mind that LoadHTMLGlob relies on template.ParseGlob which states:
When parsing multiple files with the same name in different
directories, the last one mentioned will be the one that results.
That means that if you want all of your templates to be accessible through c.HTML you need to make sure that they either have unique base names or they need to contain the {{ define "name"}} action.
Moving from the default templating system where 'everything simply worked' to Gin is a bit confusing, namely, there seem to be some naming restrictions when using files for templates. I have no idea if this is the case, but, in my setup, I had to make sure that:
The name of the template (the define keyword) needs to be the filename — at least when that's the only define in the template (I haven't tested with multiple defines) — i.e. if you're using ./templates/book.html as a template, you need to have {{ define "book.html" }} at the top of that file (this is true for templates included in other templates; I didn't experiment with blocks or other more esoteric ways to jinx templates together)
Similarly, when calling c.HTML(http.StatusOK, "book.html", book), you have to put the full name of the file containing that template (as shown!)
In other words, although the manual says otherwise, and #mkopriva confirms what the manual says, I have had a different experience: it was only I started matching the filename with the define and the c.HTML() call that I stopped getting blank pages...
Also, while running from the console (in debug mode), it was clear that my programme was 'finding' far more templates than it should — namely, almost every template was duplicated (one copy for the filename, another copy for the define, etc.). This confused not only me but the application itself...

Get {{.Host}} inside a range loop - Caddy server browse template

So I can use {{.Host}} just fine in the template file, but once inside a {{range .Items}} loop it doesn't work since it's trying to get the .Host from the .Items (array?)thing..
I get this as an error
template: listing:41:46: executing "listing" at <.Host>: can't evaluate field Host in type browse.FileInfo
I've never used Go before, I've tried reading the text template documentation page but it's all rather confusing.
ooooh, nevermind guys, I knew it was a simple fix.
{{$.Host}}
Just add the $, then you'll be using the global context again, instead of the context inside of the range loop.
Source, thanks HUGO for the clear documentation.
{{range}} changes the pipeline (the dot, .) to the current Items. You can use {{$.Host}} which will refer to the "top-level" Host.
{{$.Host}}
golang template.

Resources