feat: invokable scripts (#387)

* basic list, create, and invoke working

* all commands working

* added support for create script body from file and invoke params from file

* linter cleanup

* update defaults to existing parameters if not provided

* updated generated mock files, added mock files for scripts, added basic script create test

* added mock script list

* cleanup pass, fixed not using params in list call

* added update mock test

* fixed mock tests requiring go 1.18

* updated openapi, integrated overrides upstream, added new override to fix codegen bug

* added nil check

* fixed routes
This commit is contained in:
Andrew Depke
2022-06-22 14:08:55 -06:00
committed by GitHub
parent a68106ee88
commit 760f07ed9e
31 changed files with 3207 additions and 337 deletions

172
clients/script/script.go Normal file
View File

@ -0,0 +1,172 @@
package script
import (
"context"
"errors"
"fmt"
"github.com/influxdata/influx-cli/v2/api"
"github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influx-cli/v2/clients/query"
)
type Client struct {
clients.CLI
api.InvokableScriptsApi
}
func (c Client) printScripts(scripts []api.Script) error {
if c.PrintAsJSON {
return c.PrintJSON(scripts)
}
headers := []string{
"ID",
"Name",
"Description",
"Organization ID",
"Language",
}
var rows []map[string]interface{}
for _, s := range scripts {
row := map[string]interface{}{
"ID": *s.Id,
"Name": s.Name,
"Description": *s.Description,
"Organization ID": s.OrgID,
"Language": *s.Language,
}
rows = append(rows, row)
}
return c.PrintTable(headers, rows...)
}
type ListParams struct {
Limit int
Offset int
}
func (c Client) List(ctx context.Context, params *ListParams) error {
req := c.GetScripts(ctx)
if params != nil {
req = req.Limit(int32(params.Limit)).Offset(int32(params.Offset))
}
res, err := req.Execute()
if err != nil {
return fmt.Errorf("failed to list scripts: %v", err)
}
return c.printScripts(*res.Scripts)
}
type CreateParams struct {
Description string
Language string
Name string
Script string
}
func (c Client) Create(ctx context.Context, params *CreateParams) error {
if params == nil {
return errors.New("failed to create script: no parameters provided")
}
req := api.ScriptCreateRequest{
Name: params.Name,
Description: params.Description,
Script: params.Script,
Language: api.ScriptLanguage(params.Language),
}
script, err := c.PostScripts(ctx).ScriptCreateRequest(req).Execute()
if err != nil {
return fmt.Errorf("failed to create script: %v", err)
}
return c.printScripts([]api.Script{script})
}
type DeleteParams struct {
ScriptID string
}
func (c Client) Delete(ctx context.Context, params *DeleteParams) error {
err := c.DeleteScriptsID(ctx, params.ScriptID).Execute()
if err != nil {
return fmt.Errorf("failed to delete script: %v", err)
}
return nil
}
type RetrieveParams struct {
ScriptID string
}
func (c Client) Retrieve(ctx context.Context, params *RetrieveParams) error {
script, err := c.GetScriptsID(ctx, params.ScriptID).Execute()
if err != nil {
return fmt.Errorf("failed to retrieve script: %v", err)
}
return c.printScripts([]api.Script{script})
}
type UpdateParams struct {
ScriptID string
Description string
Name string
Script string
}
func (c Client) Update(ctx context.Context, params *UpdateParams) error {
// Retrieve the original since we might carry over some unchanged details.
oldScript, err := c.GetScriptsID(ctx, params.ScriptID).Execute()
if err != nil {
return fmt.Errorf("failed to update script: %v", err)
}
if len(params.Description) == 0 {
params.Description = *oldScript.Description
}
if len(params.Name) == 0 {
params.Name = oldScript.Name
}
if len(params.Script) == 0 {
params.Script = oldScript.Script
}
req := api.ScriptUpdateRequest{
Name: &params.Name,
Description: &params.Description,
Script: &params.Script,
}
script, err := c.PatchScriptsID(ctx, params.ScriptID).ScriptUpdateRequest(req).Execute()
if err != nil {
return fmt.Errorf("failed to update script: %v", err)
}
return c.printScripts([]api.Script{script})
}
type InvokeParams struct {
ScriptID string
Params map[string]interface{}
}
func (c Client) Invoke(ctx context.Context, params *InvokeParams) error {
req := api.ScriptInvocationParams{
Params: &params.Params,
}
resp, err := c.PostScriptsIDInvoke(ctx, params.ScriptID).ScriptInvocationParams(req).Execute()
if err != nil {
return fmt.Errorf("failed to invoke script: %v", err)
}
defer resp.Body.Close()
return query.RawResultPrinter.PrintQueryResults(resp.Body, c.StdIO)
}

View File

@ -0,0 +1,202 @@
package script_test
import (
"context"
"strings"
"testing"
"github.com/golang/mock/gomock"
"github.com/influxdata/influx-cli/v2/api"
"github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influx-cli/v2/clients/script"
"github.com/influxdata/influx-cli/v2/internal/mock"
tmock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func Test_SimpleCreate(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
scriptsApi := mock.NewMockInvocableScriptsApi(ctrl)
var (
scriptId = "123456789"
scriptName = "simple"
scriptDesc = "A basic script to be created"
scriptOrgId = "1111111111111"
scriptContent = `from(bucket: "sample_data") |> range(start: -10h)`
scriptLanguage = api.SCRIPTLANGUAGE_FLUX
)
scriptsApi.EXPECT().PostScripts(gomock.Any()).Return(api.ApiPostScriptsRequest{
ApiService: scriptsApi,
})
scriptsApi.EXPECT().PostScriptsExecute(gomock.Any()).Return(api.Script{
Id: &scriptId,
Name: scriptName,
Description: &scriptDesc,
OrgID: scriptOrgId,
Script: scriptContent,
Language: &scriptLanguage,
}, nil)
stdio := mock.NewMockStdIO(ctrl)
client := script.Client{
CLI: clients.CLI{StdIO: stdio, PrintAsJSON: true},
InvokableScriptsApi: scriptsApi,
}
stdio.EXPECT().Write(tmock.MatchedBy(func(in []byte) bool {
t.Logf("Stdio output: %s", in)
inStr := string(in)
// Verify we print the basic details of the script in some form.
return strings.Contains(inStr, scriptId) &&
strings.Contains(inStr, scriptName) &&
strings.Contains(inStr, scriptOrgId)
}))
params := script.CreateParams{
Description: scriptDesc,
Language: string(scriptLanguage),
Name: scriptName,
Script: scriptContent,
}
require.NoError(t, client.Create(context.Background(), &params))
}
func strFactory(arg interface{}) *string {
val := (arg.(string)) // Docker image runs Go 1.17, so we can't use generics here.
return &val
}
func Test_SimpleList(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
scriptsApi := mock.NewMockInvocableScriptsApi(ctrl)
language := api.SCRIPTLANGUAGE_FLUX
scripts := []api.Script{{
Id: strFactory("123456789"),
Name: "simple",
Description: strFactory("First script"),
OrgID: "1111111111111",
Script: `from(bucket: "sample_data") |> range(start: -10h)`,
Language: &language,
}, {
Id: strFactory("000000001"),
Name: "another",
Description: strFactory("Second script"),
OrgID: "9111111111119",
Script: `from(bucket: "sample_data") |> range(start: -5h)`,
Language: &language,
},
}
scriptsApi.EXPECT().GetScripts(gomock.Any()).Return(api.ApiGetScriptsRequest{
ApiService: scriptsApi,
})
scriptsApi.EXPECT().GetScriptsExecute(gomock.Any()).Return(api.Scripts{
Scripts: &scripts,
}, nil)
stdio := mock.NewMockStdIO(ctrl)
client := script.Client{
CLI: clients.CLI{StdIO: stdio, PrintAsJSON: true},
InvokableScriptsApi: scriptsApi,
}
stdio.EXPECT().Write(tmock.MatchedBy(func(in []byte) bool {
t.Logf("Stdio output: %s", in)
inStr := string(in)
// Verify we print the basic details of all scripts in some form.
success := true
for _, script := range scripts {
success = success && strings.Contains(inStr, *script.Id)
success = success && strings.Contains(inStr, script.Name)
success = success && strings.Contains(inStr, script.OrgID)
}
return success
}))
params := script.ListParams{
Limit: 10,
Offset: 0,
}
require.NoError(t, client.List(context.Background(), &params))
}
func Test_Update(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
scriptsApi := mock.NewMockInvocableScriptsApi(ctrl)
var (
scriptId = "123456789"
scriptName = "simple"
scriptDescOld = "A basic script to be created"
scriptDescNew = "An updated script"
scriptOrgId = "1111111111111"
scriptContentOld = `from(bucket: "sample_data") |> range(start: -10h)`
scriptContentNew = `from(bucket: "devbucket") |> range(start: -100h)`
scriptLanguage = api.SCRIPTLANGUAGE_FLUX
)
// If we change the logic for handling parameter defaulting, the GetScriptsID calls need to be removed.
scriptsApi.EXPECT().GetScriptsID(gomock.Any(), scriptId).Return(api.ApiGetScriptsIDRequest{
ApiService: scriptsApi,
})
scriptsApi.EXPECT().GetScriptsIDExecute(gomock.Any()).Return(api.Script{
Id: &scriptId,
Name: scriptName,
Description: &scriptDescOld,
OrgID: scriptOrgId,
Script: scriptContentOld,
Language: &scriptLanguage,
}, nil)
scriptsApi.EXPECT().PatchScriptsID(gomock.Any(), scriptId).Return(api.ApiPatchScriptsIDRequest{
ApiService: scriptsApi,
})
scriptsApi.EXPECT().PatchScriptsIDExecute(gomock.Any()).Return(api.Script{
Id: &scriptId,
Name: scriptName,
Description: &scriptDescNew,
OrgID: scriptOrgId,
Script: scriptContentNew,
Language: &scriptLanguage,
}, nil)
stdio := mock.NewMockStdIO(ctrl)
client := script.Client{
CLI: clients.CLI{StdIO: stdio, PrintAsJSON: true},
InvokableScriptsApi: scriptsApi,
}
stdio.EXPECT().Write(tmock.MatchedBy(func(in []byte) bool {
t.Logf("Stdio output: %s", in)
inStr := string(in)
// Verify we print the updated script's details.
return strings.Contains(inStr, scriptId) &&
strings.Contains(inStr, scriptName) &&
strings.Contains(inStr, scriptDescNew) &&
strings.Contains(inStr, scriptOrgId)
}))
updateParams := script.UpdateParams{
ScriptID: scriptId,
Description: scriptDescNew,
Name: scriptName,
Script: scriptContentNew,
}
require.NoError(t, client.Update(context.Background(), &updateParams))
}