// Run the configuration specified in conf against the target. This will run a series // of template fetching, applying templates, unmarshaling of the final applied template // onto the given target object. After unmarshaling is done, the target's fields are // examined one by one, by tag, and fields that have template as values are then applied. func Configure(ctx context.Context, conf Conf, target interface{}, optionalFuncs ...template.FuncMap) error { conf.lock.Lock() defer conf.lock.Unlock() initialData := ContextGetInitialData(ctx) if initialData != nil { gtemplate.ContextPutTemplateData(ctx, initialData) } contentType := ContextGetConfigDataType(ctx) // Generate a list of functions that will escape the template strings // Ex. "secret" : "{{var "zk://host/path/to/secret"}}" // This string will be escaped so that the evaluation happens after the unmarshal step by field tag funcs := template.FuncMap{} for _, fns := range optionalFuncs { funcs = gtemplate.MergeFuncMaps(funcs, fns) } // Note here we generate escaped versions of function to override what's provided. // The actual funcs are used in the struct field-by-field step, for those that are marked by tags. stubs := gtemplate.MergeFuncMaps(funcs, generateEscapeFuncsFromFieldTag(target)) conf.model = map[string]interface{}{} for _, url := range conf.Urls { // Fetch the config data and execute as if it were template applied, err := gtemplate.Execute(ctx, url, stubs) if conf.OnDoneExecuteLayer != nil { conf.OnDoneExecuteLayer(&conf, url, applied, err) } if err != nil { return err } // Unmarshal to an intermediate representation buff := bytes.NewBuffer(applied) err = encoding.Unmarshal(contentType, buff, conf.model) if conf.OnDoneUnmarshalLayer != nil { conf.OnDoneUnmarshalLayer(&conf, url, err) } if err != nil { return err } } if conf.OnDoneFetching != nil { conf.OnDoneFetching(&conf) } // Now marshal the aggregated model to a buffer and then unmarshal it back to the typed struct serialized := new(bytes.Buffer) err := encoding.Marshal(contentType, serialized, conf.model) if err != nil { return err } conf.serialized = serialized.Bytes() err = encoding.Unmarshal(contentType, bytes.NewBuffer(conf.serialized), target) if conf.OnDoneSerialize != nil { conf.OnDoneSerialize(&conf, err) } if err != nil { return err } if conf.OnDoneUnmarshal != nil { conf.OnDoneUnmarshal(&conf, target) } // Now look for fields with struct tags and apply the actual templates return evalStructFieldTemplates(target, funcs) }
func (suite *TestSuiteTemplate) TestTemplateExecuteWithVarBlock(c *C) { // Write something url := "zk://" + strings.Join(Hosts(), ",") zk, err := namespace.Dial(context.Background(), url) c.Assert(err, IsNil) c.Log(zk) k := "/unit-test/registry/template/test-template-execute-vars/PG_PASS" p := namespace.NewPath(k) err = zk.Delete(p) // Write new data v := []byte("password") _, err = zk.Put(p, v, false) c.Assert(err, IsNil) zk.Close() // Now use the value in zk in the template // Generate the auth token required by the server. token := auth.NewToken(1*time.Hour).Add("secure", 1) header := http.Header{} token.SetHeader(header, testutil.PrivateKeyFunc) // This is the content to be served by the test server. // Using the Execute will add additional functions that can be included in the var blocks. suite.template = ` {{define "comments"}} # The variable define blocks allow the definition of variables that are reused throughout the # main body of the template. The variables are referenced as '<blockname>.<fieldname>'. {{end}} {{define "app"}} # blockname is 'app' version: 1.2 image: repo build: 1234 user: "******"USER"}}" # Getting the environment variable and use that as value. password: "******"zk:///unit-test/registry/template/test-template-execute-vars/PG_PASS" }}" {{end}} {{define "host"}} label: appserver name: myhost port: {{.port}} # Here we allow the application to pass in a context that's refereceable. {{end}} { "image" : "repo/myapp:` + "{{my `app.version`}}-{{my `app.build`}}" + `", "host" : "` + "{{my `host.name`}}" + `",{{/* use this for comment in JSON :) */}} "user" : "` + "{{my `app.user`}}" + `", "password" : "` + "{{my `app.password`}}" + `", "port" : "` + "{{my `host.port`}}" + `" }` // The url is a template too. url = "http://localhost:{{.port}}/secure" data := map[string]interface{}{ "Name": "test", "Age": 20, "port": suite.port, } ctx := template.ContextPutTemplateData(resource.ContextPutHttpHeader(context.Background(), header), data) text, err := template.Execute(ctx, url) c.Assert(err, IsNil) c.Log(string(text)) obj := make(map[string]string) err = json.Unmarshal(text, &obj) c.Assert(err, IsNil) c.Assert(obj["image"], Equals, "repo/myapp:1.2-1234") c.Assert(obj["user"], Equals, os.Getenv("USER")) c.Assert(obj["host"], Equals, "myhost") c.Assert(obj["port"], Equals, fmt.Sprintf("%d", suite.port)) c.Assert(obj["password"], Equals, string(v)) }