Skip to content

Commit

Permalink
Embed templates into binary (#2169)
Browse files Browse the repository at this point in the history
* Embed config templates

* Embed export templates

* Embed graph templates

* Just call the export Template "export"

* Use path "__full" for builtin full export template

* fix embedded static files

* rewrite for switch

* align docs with the export template and graph changes

---------

Co-authored-by: Roman Dodin <[email protected]>
  • Loading branch information
remram44 and hellt authored Aug 22, 2024
1 parent 6ad6256 commit 26445c5
Show file tree
Hide file tree
Showing 34 changed files with 76 additions and 373 deletions.
16 changes: 13 additions & 3 deletions clab/config/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package config

import (
"context"
"embed"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"text/template"
Expand Down Expand Up @@ -51,20 +54,27 @@ func LoadTemplates(tmpl *template.Template, role string) error {
return nil
}

//go:embed templates
var embeddedTemplates embed.FS

func RenderAll(allnodes map[string]*NodeConfig) error {
if len(TemplatePaths) == 0 { // default is the install path
TemplatePaths = []string{"@"}
}

for i, v := range TemplatePaths {
var TemplateFS []fs.FS

for _, v := range TemplatePaths {
if v == "@" {
TemplatePaths[i] = "/etc/containerlab/templates/"
TemplateFS = append(TemplateFS, embeddedTemplates)
} else {
TemplateFS = append(TemplateFS, os.DirFS(v))
}
}

if len(TemplateNames) == 0 {
var err error
TemplateNames, err = GetTemplateNamesInDirs(TemplatePaths)
TemplateNames, err = GetTemplateNamesInDirs(TemplateFS)
if err != nil {
return err
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
10 changes: 5 additions & 5 deletions clab/config/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package config

import (
"fmt"
"io/fs"
"net/netip"
"path/filepath"
"reflect"
"strconv"
"strings"
Expand Down Expand Up @@ -274,15 +274,15 @@ func ipFarEnd(in netip.Prefix) netip.Prefix {
// GetTemplateNamesInDirs returns a list of template file names found in a list of dir `paths`
// without traversing nested dirs
// template names are following the pattern <some-name>__<role/kind>.tmpl.
func GetTemplateNamesInDirs(paths []string) ([]string, error) {
func GetTemplateNamesInDirs(dirs []fs.FS) ([]string, error) {
var tnames []string
for _, p := range paths {
all, err := filepath.Glob(filepath.Join(p, "*__*.tmpl"))
for _, dir := range dirs {
all, err := fs.Glob(dir, "*__*.tmpl")
if err != nil {
return nil, err
}
for _, fn := range all {
tn := strings.Split(filepath.Base(fn), "__")[0]
tn := strings.Split(fn, "__")[0]
// skip adding templates with the same name
if len(tnames) > 0 && tnames[len(tnames)-1] == tn {
continue
Expand Down
26 changes: 21 additions & 5 deletions clab/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ package clab

import (
"context"
_ "embed"
"encoding/json"
"io"
"path/filepath"
"text/template"

"github.com/hairyhenderson/gomplate/v3"
Expand Down Expand Up @@ -41,10 +41,15 @@ type TopologyExport struct {
NodeConfigs map[string]*types.NodeConfig `json:"nodeconfigs,omitempty"`
}

//go:embed export_templates/auto.tmpl
var defaultExportTemplate string

//go:embed export_templates/full.tmpl
var fullExportTemplate string

// exportTopologyDataWithTemplate generates and writes topology data file to w using a template.
func (c *CLab) exportTopologyDataWithTemplate(_ context.Context, w io.Writer, p string) error {
n := filepath.Base(p)
t, err := template.New(n).
t := template.New("export").
Funcs(gomplate.CreateFuncs(context.Background(), new(data.Data))).
Funcs(template.FuncMap{
"ToJSON": func(v interface{}) string {
Expand All @@ -55,8 +60,19 @@ func (c *CLab) exportTopologyDataWithTemplate(_ context.Context, w io.Writer, p
a, _ := json.MarshalIndent(v, prefix, indent)
return string(a)
},
}).
ParseFiles(p)
})

var err error

switch {
case p != "":
_, err = t.ParseFiles(p)
case p == "__full":
_, err = t.Parse(fullExportTemplate)
default:
_, err = t.Parse(defaultExportTemplate)
}

if err != nil {
return err
}
Expand Down
File renamed without changes.
File renamed without changes.
40 changes: 31 additions & 9 deletions clab/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
package clab

import (
"embed"
"fmt"
"html/template"
"io/fs"
"net/http"
"os"
"os/exec"
Expand Down Expand Up @@ -43,7 +45,7 @@ type TopoData struct {
// noListFs embeds the http.Dir to override the Open method of a filesystem
// to prevent listing of static files, see https://github.com/srl-labs/containerlab/pull/802#discussion_r815373751
type noListFs struct {
http.Dir
http.FileSystem
}

var g *gographviz.Graph
Expand Down Expand Up @@ -154,7 +156,7 @@ func commandExists(cmd string) bool {
// Open is a custom FS opener that prevents listing of the files in the filesystem
// see https://github.com/srl-labs/containerlab/pull/802#discussion_r815373751
func (nfs noListFs) Open(name string) (result http.File, err error) {
f, err := nfs.Dir.Open(name)
f, err := nfs.FileSystem.Open(name)
if err != nil {
return
}
Expand Down Expand Up @@ -246,24 +248,44 @@ func (c *CLab) GenerateMermaidGraph(direction string) error {
return nil
}

//go:embed graph_templates/nextui/nextui.html
var defaultTemplate string

//go:embed graph_templates/nextui/static
var defaultStatic embed.FS

func (c *CLab) ServeTopoGraph(tmpl, staticDir, srv string, topoD TopoData) error {
var t *template.Template

if !utils.FileExists(tmpl) {
if tmpl == "" {
t = template.Must(template.New("nextui.html").Parse(defaultTemplate))
} else if utils.FileExists(tmpl) {
t = template.Must(template.ParseFiles(tmpl))
} else {
return fmt.Errorf("%w. Path %s", e.ErrFileNotFound, tmpl)
}
t = template.Must(template.ParseFiles(tmpl))

if staticDir != "" {
if tmpl == "" {
return fmt.Errorf("the --static-dir flag must be used with the --template flag")
if staticDir != "" && tmpl == "" {
return fmt.Errorf("the --static-dir flag must be used with the --template flag")
}

var staticFS http.FileSystem
if staticDir == "" {
// extract the sub fs with static files from the embedded fs
subFS, err := fs.Sub(defaultStatic, "graph_templates/nextui/static")
if err != nil {
return err
}

fs := http.FileServer(noListFs{http.Dir(staticDir)})
http.Handle("/static/", http.StripPrefix("/static/", fs))
staticFS = http.FS(subFS)
} else {
log.Infof("Serving static files from directory: %s", staticDir)
staticFS = http.Dir(staticDir)
}

fs := http.FileServer(noListFs{staticFS})
http.Handle("/static/", http.StripPrefix("/static/", fs))

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_ = t.Execute(w, topoD)
})
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
7 changes: 1 addition & 6 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ import (
"github.com/srl-labs/containerlab/runtime"
)

const (
// file name of a topology export data.
defaultExportTemplateFPath = "/etc/containerlab/templates/export/auto.tmpl"
)

// name of the container management network.
var mgmtNetName string

Expand Down Expand Up @@ -74,7 +69,7 @@ func init() {
"limit the maximum number of workers creating nodes and virtual wires")
deployCmd.Flags().BoolVarP(&skipPostDeploy, "skip-post-deploy", "", false, "skip post deploy action")
deployCmd.Flags().StringVarP(&exportTemplate, "export-template", "",
defaultExportTemplateFPath, "template file for topology data export")
"", "template file for topology data export")
deployCmd.Flags().StringSliceVarP(&nodeFilter, "node-filter", "", []string{},
"comma separated list of nodes to include")
deployCmd.Flags().BoolVarP(&skipLabDirFileACLs, "skip-labdir-acl", "", false,
Expand Down
9 changes: 2 additions & 7 deletions cmd/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ import (
"github.com/srl-labs/containerlab/types"
)

const (
defaultGraphTemplatePath = "/etc/containerlab/templates/graph/nextui/nextui.html"
defaultStaticPath = "/etc/containerlab/templates/graph/nextui/static"
)

var (
srv string
tmpl string
Expand Down Expand Up @@ -159,9 +154,9 @@ func init() {
graphCmd.Flags().BoolVarP(&drawio, "drawio", "", false, "generate drawio diagram file")
graphCmd.Flags().StringVarP(&drawioVersion, "drawio-version", "", "latest",
"version of the clab-io-draw container to use for generating drawio diagram file")
graphCmd.Flags().StringVarP(&tmpl, "template", "", defaultGraphTemplatePath,
graphCmd.Flags().StringVarP(&tmpl, "template", "", "",
"Go html template used to generate the graph")
graphCmd.Flags().StringVarP(&staticDir, "static-dir", "", defaultStaticPath,
graphCmd.Flags().StringVarP(&staticDir, "static-dir", "", "",
"Serve static files from the specified directory")
graphCmd.Flags().StringSliceVarP(&nodeFilter, "node-filter", "", []string{},
"comma separated list of nodes to include")
Expand Down
4 changes: 2 additions & 2 deletions docs/cmd/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ The default timeout is set to 2 minutes and can be changed to values like `30s,

#### export-template

The local `--export-template` flag allows a user to specify a custom Go template that will be used for exporting topology data into `topology-data.json` file under the lab directory. If not set, the default template path is `/etc/containerlab/templates/export/auto.tmpl`.
The local `--export-template` flag allows a user to specify a custom Go template that will be used for exporting topology data into `topology-data.json` file under the lab directory. If not set, the [default template](https://github.com/srl-labs/containerlab/blob/main/clab/export_templates/auto.tmpl) is used.

To export full topology data instead of a subset of fields exported by default, use `--export-template /etc/containerlab/templates/export/full.tmpl`. Note, some fields exported via `full.tmpl` might contain sensitive information like TLS private keys. To customize export data, it is recommended to start with a copy of `auto.tmpl` and change it according to your needs.
To export the full topology data instead of a subset of fields exported by default, use `--export-template __full` which is a special value that instructs containerlab to use the [full.tmpl](https://github.com/srl-labs/containerlab/blob/main/clab/export_templates/full.tmpl) template file. Note, some fields exported via `full.tmpl` might contain sensitive information like TLS private keys. To customize export data, it is recommended to start with a copy of `auto.tmpl` and change it according to your needs.

#### log-level

Expand Down
2 changes: 1 addition & 1 deletion docs/cmd/graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ A single path `/` is served, where the graph is generated based on either a defa

### template

The `--template` flag allows to customize the HTML based graph by supplying a user defined template that will be rendered and exposed on the address specified by `--srv`.
The `--template` flag allows to customize the HTML-based graph by supplying a user-defined template that will be rendered and exposed on the address specified by the `--srv`.

### static-dir

Expand Down
Loading

0 comments on commit 26445c5

Please sign in to comment.