github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/generators/client/client.go (about)

     1  // Copyright (c) 2019-2021, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package client
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"fmt"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"strings"
    15  	"text/template"
    16  
    17  	"github.com/choria-io/go-choria/internal/fs"
    18  	addl "github.com/choria-io/go-choria/providers/agent/mcorpc/ddl/agent"
    19  	"github.com/choria-io/go-choria/providers/agent/mcorpc/ddl/common"
    20  
    21  	"github.com/sirupsen/logrus"
    22  	"golang.org/x/tools/imports"
    23  )
    24  
    25  type Generator struct {
    26  	agent *agent
    27  
    28  	DDLFile     string
    29  	OutDir      string
    30  	PackageName string
    31  }
    32  
    33  type agent struct {
    34  	Package string
    35  	DDL     *addl.DDL
    36  	RawDDL  string // raw text of the JSON DDL file
    37  }
    38  
    39  func (a *agent) ActionRequiredInputs(act string) map[string]*common.InputItem {
    40  	inputs := make(map[string]*common.InputItem)
    41  
    42  	for _, act := range a.DDL.Actions {
    43  		for name, input := range act.Input {
    44  			if !input.Optional {
    45  				inputs[name] = input
    46  			}
    47  		}
    48  	}
    49  
    50  	return inputs
    51  }
    52  
    53  func (g *Generator) writeActions() error {
    54  	code, err := fs.FS.ReadFile("client/action.templ")
    55  	if err != nil {
    56  		return err
    57  	}
    58  
    59  	type action struct {
    60  		Agent             *agent
    61  		Package           string
    62  		AgentName         string
    63  		ActionName        string
    64  		ActionDescription string
    65  		OutputNames       []string
    66  		InputNames        []string
    67  		RequiredInputs    map[string]*common.InputItem
    68  		OptionalInputs    map[string]*common.InputItem
    69  		Outputs           map[string]*common.OutputItem
    70  	}
    71  
    72  	for _, actname := range g.agent.DDL.ActionNames() {
    73  		actint, err := g.agent.DDL.ActionInterface(actname)
    74  		if err != nil {
    75  			return err
    76  		}
    77  
    78  		outfile := filepath.Join(g.OutDir, fmt.Sprintf("action_%s.go", actint.Name))
    79  		logrus.Infof("Writing %s for action %s", outfile, actint.Name)
    80  
    81  		out, err := os.Create(outfile)
    82  		if err != nil {
    83  			return err
    84  		}
    85  		defer out.Close()
    86  
    87  		act := &action{
    88  			Agent:             g.agent,
    89  			Package:           g.agent.Package,
    90  			AgentName:         g.agent.DDL.Metadata.Name,
    91  			ActionName:        actint.Name,
    92  			ActionDescription: actint.Description,
    93  			InputNames:        actint.InputNames(),
    94  			OutputNames:       actint.OutputNames(),
    95  			RequiredInputs:    g.optionalInputSelect(actint, false),
    96  			OptionalInputs:    g.optionalInputSelect(actint, true),
    97  			Outputs:           actint.Output,
    98  		}
    99  
   100  		tpl := template.Must(template.New(actint.Name).Funcs(g.funcMap()).Parse(string(code)))
   101  		err = tpl.Execute(out, act)
   102  		if err != nil {
   103  			return err
   104  		}
   105  
   106  		err = FormatGoSource(outfile)
   107  		if err != nil {
   108  			return err
   109  		}
   110  	}
   111  
   112  	ddlPath := filepath.Join(g.OutDir, "ddl.json")
   113  	cDDL := bytes.NewBuffer([]byte{})
   114  	json.Compact(cDDL, []byte(g.agent.RawDDL))
   115  	logrus.Infof("Writing %s", ddlPath)
   116  	err = os.WriteFile(filepath.Join(g.OutDir, "ddl.json"), cDDL.Bytes(), 0644)
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	return nil
   122  }
   123  
   124  func (g *Generator) writeBasics() error {
   125  	dir, err := fs.FS.ReadDir("client")
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	for _, file := range dir {
   131  		t := strings.TrimSuffix(filepath.Base(file.Name()), filepath.Ext(file.Name()))
   132  		if t == "action" {
   133  			continue
   134  		}
   135  
   136  		outfile := path.Join(g.OutDir, t+".go")
   137  		logrus.Infof("Writing %s", outfile)
   138  		out, err := os.Create(outfile)
   139  		if err != nil {
   140  			return err
   141  		}
   142  
   143  		code, err := fs.FS.ReadFile(filepath.Join("client", file.Name()))
   144  		if err != nil {
   145  			return err
   146  		}
   147  
   148  		tpl := template.Must(template.New(t).Funcs(g.funcMap()).Parse(string(code)))
   149  
   150  		err = tpl.Execute(out, g.agent)
   151  		if err != nil {
   152  			return err
   153  		}
   154  
   155  		err = FormatGoSource(outfile)
   156  		if err != nil {
   157  			return err
   158  		}
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  func FormatGoSource(f string) error {
   165  	bs, err := os.ReadFile(f)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	opt := imports.Options{
   170  		Comments:   true,
   171  		FormatOnly: true,
   172  	}
   173  	bs, err = imports.Process(f, bs, &opt)
   174  	if err != nil {
   175  		return err
   176  	}
   177  	return os.WriteFile(f, bs, os.ModePerm)
   178  }
   179  
   180  func (g *Generator) GenerateClient() error {
   181  	var err error
   182  
   183  	g.agent = &agent{}
   184  	g.agent.DDL, err = addl.New(g.DDLFile)
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	if g.agent.DDL == nil {
   190  		return fmt.Errorf("could not find any DDL")
   191  	}
   192  
   193  	raw, err := os.ReadFile(g.DDLFile)
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	g.agent.RawDDL = string(raw)
   199  	g.agent.Package = g.PackageName
   200  
   201  	if g.PackageName == "" {
   202  		g.agent.Package = strings.ToLower(g.agent.DDL.Metadata.Name) + "client"
   203  	}
   204  
   205  	logrus.Infof("Writing Choria Client for Agent %s Version %s to %s", g.agent.DDL.Metadata.Name, g.agent.DDL.Metadata.Version, g.OutDir)
   206  	err = g.writeActions()
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	err = g.writeBasics()
   212  	if err != nil {
   213  		return err
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  func (g *Generator) optionalInputSelect(action *addl.Action, opt bool) map[string]*common.InputItem {
   220  	inputs := make(map[string]*common.InputItem)
   221  
   222  	for name, act := range action.Input {
   223  		if act.Optional == opt {
   224  			inputs[name] = act
   225  		}
   226  	}
   227  
   228  	return inputs
   229  }