Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions internal/flags/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@ type TemplateGetOptions struct {
func (o *TemplateGetOptions) Flags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&o.All, "all", o.All, "Include all workflows")
}

type TemplateLoadOptions struct {
Type string
Group string
Include []string
}

func (o *TemplateLoadOptions) Flags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Type, "type", o.Type, "Type of template to load (valid values are textfsm, native)")
cmd.Flags().StringVar(&o.Group, "group", o.Group, "Group to load templates into (only valid with --type=textfsm)")
cmd.Flags().StringArrayVar(&o.Include, "include", o.Include, "Include files matching pattern")
}
4 changes: 4 additions & 0 deletions internal/flags/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ func TestTemplateCreateOptions(t *testing.T) {
func TestTemplateGetOptions(t *testing.T) {
checkFlags(t, &TemplateGetOptions{}, []string{"all"})
}

func TestTemplateLoadOptions(t *testing.T) {
checkFlags(t, &TemplateLoadOptions{}, []string{"type", "group", "include"})
}
12 changes: 12 additions & 0 deletions internal/handlers/descriptors/templates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,15 @@ import:
group: automation-studio
description: |
Import a template

load:
use: templates <path>
group: automation-studio
description: |
Load templates

dump:
use: templates
group: automation-studio
description: |
Dump all templates
4 changes: 4 additions & 0 deletions internal/handlers/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func withOptions(f *AssetHandlerFlags) CommandRunnerOption {
c.Options = f.Import
case "export":
c.Options = f.Export
case "load":
c.Options = f.Load
case "dump":
c.Options = f.Dump
}
}
}
Expand Down
1 change: 1 addition & 0 deletions internal/handlers/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func NewTemplateHandler(r Runtime, desc Descriptors) AssetHandler {
&AssetHandlerFlags{
Create: &flags.TemplateCreateOptions{},
Get: &flags.TemplateGetOptions{},
Load: &flags.TemplateLoadOptions{},
},
)
}
4 changes: 4 additions & 0 deletions internal/runners/importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func importGetPathFromRequest(in Request) (string, error) {

path := in.Args[0]

if dir, err := utils.IsDir(path); err != nil || !dir {
return "", fmt.Errorf("path `%s` is not a directory", path)
}

if in.Common.(flags.Gitter).GetRepository() != "" {
r, err := importNewRepositoryFromRequest(in)
if err != nil {
Expand Down
33 changes: 33 additions & 0 deletions internal/runners/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ import (
"os"
"path/filepath"

"github.com/itential/ipctl/internal/utils"
"github.com/itential/ipctl/pkg/logger"
)

type LoadOptions struct {
Include []string
Exclude []string
}

// loadAssets receives the Request argument and loads the assets from disk.
func loadAssets(in Request) (map[string]interface{}, error) {
logger.Trace()
Expand Down Expand Up @@ -39,6 +45,33 @@ func loadAssets(in Request) (map[string]interface{}, error) {
return data, nil
}

func loadStringAssets(in Request, options LoadOptions) (map[string]interface{}, error) {
logger.Trace()

path, err := importGetPathFromRequest(in)
if err != nil {
return nil, err
}

files, err := os.ReadDir(path)
if err != nil {
return nil, err
}

var data = make(map[string]interface{})

for _, ele := range files {
res, err := utils.ReadStringFromFile(filepath.Join(path, ele.Name()))
if err != nil {
return nil, err
}
data[ele.Name()] = res
}

return data, nil

}

// loadUnmarshalAsset unmarshals the interface in argument to ptr
func loadUnmarshalAsset(in interface{}, ptr any) error {
logger.Trace()
Expand Down
162 changes: 142 additions & 20 deletions internal/runners/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ package runners
import (
"errors"
"fmt"
"path/filepath"
"strings"

"github.com/itential/ipctl/internal/flags"
"github.com/itential/ipctl/internal/terminal"
"github.com/itential/ipctl/pkg/client"
"github.com/itential/ipctl/pkg/config"
"github.com/itential/ipctl/pkg/logger"
Expand All @@ -28,9 +30,11 @@ func NewTemplateRunner(c client.Client, cfg *config.Config) *TemplateRunner {
}
}

//////////////////////////////////////////////////////////////////////////////
// Reader Interface
//
/*
*******************************************************************************
Reader interface
*******************************************************************************
*/

// Get implements the `get command-templates` command
func (r *TemplateRunner) Get(in Request) (*Response, error) {
Expand Down Expand Up @@ -86,9 +90,11 @@ func (r *TemplateRunner) Describe(in Request) (*Response, error) {
}, nil
}

//////////////////////////////////////////////////////////////////////////////
// Writer Interface
//
/*
*******************************************************************************
Writer interface
*******************************************************************************
*/

// Create implements the `create template ...` command
func (r *TemplateRunner) Create(in Request) (*Response, error) {
Expand Down Expand Up @@ -154,20 +160,23 @@ func (r *TemplateRunner) Clear(in Request) (*Response, error) {
}

for _, ele := range elements {
terminal.Display("Deleting template `%s` (%s)", ele.Name, ele.Id)
if err := r.service.Delete(ele.Id); err != nil {
logger.Debug("failed to delete template `%s` (%s)", ele.Name, ele.Id)
return nil, err
}
}

return &Response{
Text: fmt.Sprintf("Deleted %v template(s)", len(elements)),
Text: fmt.Sprintf("\nDeleted %v template(s)", len(elements)),
}, nil
}

//////////////////////////////////////////////////////////////////////////////
// Copier Interface
//
/*
*******************************************************************************
Copier interface
*******************************************************************************
*/

func (r *TemplateRunner) Copy(in Request) (*Response, error) {
logger.Trace()
Expand Down Expand Up @@ -233,9 +242,11 @@ func (r *TemplateRunner) CopyTo(profile string, in any, replace bool) (any, erro

}

//////////////////////////////////////////////////////////////////////////////
// Importer Interface
//
/*
*******************************************************************************
Importer interface
*******************************************************************************
*/

func (r *TemplateRunner) Import(in Request) (*Response, error) {
logger.Trace()
Expand All @@ -257,9 +268,11 @@ func (r *TemplateRunner) Import(in Request) (*Response, error) {
}, nil
}

//////////////////////////////////////////////////////////////////////////////
// Exporter Interface
//
/*
*******************************************************************************
Exporter interface
*******************************************************************************
*/

func (r *TemplateRunner) Export(in Request) (*Response, error) {
logger.Trace()
Expand Down Expand Up @@ -287,16 +300,123 @@ func (r *TemplateRunner) Export(in Request) (*Response, error) {
}, nil
}

//////////////////////////////////////////////////////////////////////////////
// Private functions
//
/*
*******************************************************************************
Dumper interface
*******************************************************************************
*/

// Dump implements the `dump templates...` command
func (r *TemplateRunner) Dump(in Request) (*Response, error) {
logger.Trace()

res, err := r.service.GetAll()
if err != nil {
return nil, err
}

var assets = map[string]interface{}{}

for _, ele := range res {
if !strings.HasPrefix(ele.Name, "@") {
key := fmt.Sprintf("%s.template.json", ele.Name)
assets[key] = ele
}
}

if err := dumpAssets(in, assets); err != nil {
return nil, err
}

return &Response{
Text: fmt.Sprintf("Dumped %v template(s)", len(assets)),
}, nil
}

/*
*******************************************************************************
Loader interface
*******************************************************************************
*/

// Load implements the `load template ...` command
func (r *TemplateRunner) Load(in Request) (*Response, error) {
logger.Trace()

options := in.Options.(*flags.TemplateLoadOptions)

var elements map[string]interface{}
var err error

if options.Type == "textfsm" {
elements, err = loadStringAssets(in, LoadOptions{})
} else {
elements, err = loadAssets(in)
if err != nil {
return nil, err
}
}

var loaded int
var skipped int

for fn, ele := range elements {
var template services.Template
var err error

if options.Type == "textfsm" || options.Type == "jinja2" {
name := strings.TrimSuffix(fn, filepath.Ext(fn))
template = services.NewTemplate(name, "Imported", "", options.Type)
template.Template = ele.(string)
} else {
err = loadUnmarshalAsset(ele, &template)
if err != nil {
terminal.Display("Failed to load template from `%s`, skipping", fn)
skipped++
}
}

if err == nil {
if err := r.importTemplate(template, false); err != nil {
if !strings.HasPrefix(err.Error(), "template with name") {
return nil, err
}
terminal.Display("Skipping `%s`, template `%s` already exists", fn, template.Name)
skipped++
} else {
terminal.Display("Loaded template `%s` successfully from `%s`", template.Name, fn)
loaded++
}
}
}

output := fmt.Sprintf("\nSuccessfully loaded %v and skipped %v files from `%s`", loaded, skipped, in.Args[0])

return &Response{
Text: output,
}, nil

}

/*
*******************************************************************************
Private functions
*******************************************************************************
*/

func (r TemplateRunner) importTemplate(in services.Template, replace bool) error {
logger.Trace()
logger.Debug("attempting to import template `%s`", in.Name)

p, err := r.service.GetByName(in.Name)
if err == nil {
if err != nil {
if err.Error() != "template not found" {
return err
}
}
if p != nil {
if replace {
logger.Debug("template exists, deleting it")
r.service.Delete(p.Id)
} else {
return errors.New(fmt.Sprintf("template with name `%s` already exists, use `--replace` to overwrite", p.Name))
Expand All @@ -308,5 +428,7 @@ func (r TemplateRunner) importTemplate(in services.Template, replace bool) error
return err
}

logger.Debug("successfully imported template `%s`", in.Name)

return nil
}
9 changes: 9 additions & 0 deletions internal/utils/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,12 @@ func EnsurePathExists(p string) error {
}
return nil
}

// IsDir will check the path argument to see if it is a directory or not.
func IsDir(path string) (bool, error) {
info, err := os.Stat(path)
if err != nil {
return false, err // Return false if the path doesn't exist or there's another error
}
return info.IsDir(), nil
}