-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
create attestation type command (#376)
* Walking skeleton * Add test for successfully creating a new custom attestation type * Add description flag to custom attestation creation command * Send the schema * Send JQ evaluators * Send JQ evaluator rules * Add docs * Hide commnad --------- Co-authored-by: JonJagger <[email protected]> Co-authored-by: Simon Castagna <[email protected]>
- Loading branch information
1 parent
9b6094f
commit af4d109
Showing
6 changed files
with
211 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net/http" | ||
|
||
"github.com/kosli-dev/cli/internal/requests" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
const createAttestationTypeShortDesc = `Create or update a Kosli attestation type.` | ||
|
||
const createAttestationTypeLongDesc = createAttestationTypeShortDesc + ` | ||
^TYPE-NAME^ must start with a letter or number, and only contain letters, numbers, ^.^, ^-^, ^_^, and ^~^. | ||
^--schema^ is a path to a file containing a JSON schema which will be used to validate attestations made using this type. | ||
^--jq^ defines the evaluation rules for this attestation type. This can be repeated in order to add additional rules. All rules must return ^true^ for the evaluation to pass. | ||
` | ||
|
||
const createAttestationTypeExample = ` | ||
kosli create attestation type person-of-age \ | ||
--description "Attest that a person meets the age requirements." \ | ||
--schema person-schema.json \ | ||
--jq ".age >= 18" | ||
--jq ".age < 65" | ||
` | ||
|
||
type createAttestationTypeOptions struct { | ||
payload CreateAttestationTypePayload | ||
schemaFilePath string | ||
jqRules []string | ||
} | ||
|
||
type JQEvaluatorPayload struct { | ||
ContentType string `json:"content_type"` | ||
Rules []string `json:"rules"` | ||
} | ||
|
||
func NewJQEvaluatorPayload(rules []string) *JQEvaluatorPayload { | ||
return &JQEvaluatorPayload{"jq", rules} | ||
} | ||
|
||
type CreateAttestationTypePayload struct { | ||
TypeName string `json:"name"` | ||
Description string `json:"description,omitempty"` | ||
Evaluator *JQEvaluatorPayload `json:"evaluator,omitempty"` | ||
} | ||
|
||
func newCreateAttestationTypeCmd(out io.Writer) *cobra.Command { | ||
o := new(createAttestationTypeOptions) | ||
cmd := &cobra.Command{ | ||
Use: "attestation-type TYPE-NAME", | ||
Short: createAttestationTypeShortDesc, | ||
Long: createAttestationTypeLongDesc, | ||
Example: createAttestationTypeExample, | ||
Args: cobra.ExactArgs(1), | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
err := RequireGlobalFlags(global, []string{"Org", "ApiToken"}) | ||
if err != nil { | ||
return ErrorBeforePrintingUsage(cmd, err.Error()) | ||
} | ||
return nil | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return o.run(args) | ||
}, | ||
Hidden: true, | ||
} | ||
|
||
cmd.Flags().StringVarP(&o.payload.Description, "description", "d", "", attestationTypeDescriptionFlag) | ||
cmd.Flags().StringVarP(&o.schemaFilePath, "schema", "s", "", attestationTypeSchemaFlag) | ||
cmd.Flags().StringArrayVar(&o.jqRules, "jq", []string{}, attestationTypeJqFlag) | ||
|
||
addDryRunFlag(cmd) | ||
return cmd | ||
} | ||
|
||
func (o *createAttestationTypeOptions) run(args []string) error { | ||
o.payload.TypeName = args[0] | ||
if len(o.jqRules) > 0 { | ||
o.payload.Evaluator = NewJQEvaluatorPayload(o.jqRules) | ||
} | ||
|
||
form, err := prepareAttestationTypeForm(o.payload, o.schemaFilePath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
url := fmt.Sprintf("%s/api/v2/custom-attestation-types/%s", global.Host, global.Org) | ||
reqParams := &requests.RequestParams{ | ||
Method: http.MethodPost, | ||
URL: url, | ||
Form: form, | ||
DryRun: global.DryRun, | ||
Token: global.ApiToken, | ||
} | ||
_, err = kosliClient.Do(reqParams) | ||
if err == nil && !global.DryRun { | ||
logger.Info("attestation-type %s was created", o.payload.TypeName) | ||
} | ||
return err | ||
} | ||
|
||
func prepareAttestationTypeForm(payload interface{}, schemaFilePath string) ([]requests.FormItem, error) { | ||
form, err := newAttestationTypeForm(payload, schemaFilePath) | ||
if err != nil { | ||
return []requests.FormItem{}, err | ||
} | ||
return form, nil | ||
} | ||
|
||
// newAttestationTypeForm constructs a list of FormItems for an attestation-type | ||
// form submission. | ||
func newAttestationTypeForm(payload interface{}, schemaFilePath string) ( | ||
[]requests.FormItem, error, | ||
) { | ||
form := []requests.FormItem{ | ||
{Type: "field", FieldName: "data_json", Content: payload}, | ||
} | ||
|
||
if schemaFilePath != "" { | ||
form = append(form, requests.FormItem{Type: "file", FieldName: "type_schema", Content: schemaFilePath}) | ||
} | ||
|
||
return form, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
// Define the suite, and absorb the built-in basic suite | ||
// functionality from testify - including a T() method which | ||
// returns the current testing context | ||
type CreateAttestationTypeTestSuite struct { | ||
suite.Suite | ||
defaultKosliArguments string | ||
} | ||
|
||
func (suite *CreateAttestationTypeTestSuite) SetupTest() { | ||
global = &GlobalOpts{ | ||
ApiToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImNkNzg4OTg5In0.e8i_lA_QrEhFncb05Xw6E_tkCHU9QfcY4OLTVUCHffY", | ||
Org: "docs-cmd-test-user", | ||
Host: "http://localhost:8001", | ||
} | ||
suite.defaultKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) | ||
} | ||
|
||
func (suite *CreateAttestationTypeTestSuite) TestCustomAttestationTypeCmd() { | ||
tests := []cmdTestCase{ | ||
{ | ||
wantError: true, | ||
name: "fails when no arguments are provided", | ||
cmd: "create attestation-type" + suite.defaultKosliArguments, | ||
golden: "Error: accepts 1 arg(s), received 0\n", | ||
}, | ||
{ | ||
name: "type name is provided", | ||
cmd: "create attestation-type wibble" + suite.defaultKosliArguments, | ||
golden: "attestation-type wibble was created\n", | ||
}, | ||
{ | ||
name: "type description is provided", | ||
cmd: "create attestation-type wibble-2 --description 'description of attestation type'" + suite.defaultKosliArguments, | ||
golden: "attestation-type wibble-2 was created\n", | ||
}, | ||
{ | ||
name: "type schema is provided", | ||
cmd: "create attestation-type wibble-4 --schema testdata/person-schema.json" + suite.defaultKosliArguments, | ||
golden: "attestation-type wibble-4 was created\n", | ||
}, | ||
{ | ||
name: "type jq evaluator is provided", | ||
cmd: `create attestation-type wibble-5 --jq '.age > 21' --jq '.age < 50'` + suite.defaultKosliArguments, | ||
golden: "attestation-type wibble-5 was created\n", | ||
}, | ||
{ | ||
name: `jq evaluators can include bare "`, | ||
cmd: `create attestation-type wibble-6 --jq '.name | startswith("B")'` + suite.defaultKosliArguments, | ||
golden: "attestation-type wibble-6 was created\n", | ||
}, | ||
} | ||
|
||
runTestCmd(suite.T(), tests) | ||
} | ||
|
||
// In order for 'go test' to run this suite, we need to create | ||
// a normal test function and pass our suite to suite.Run | ||
func TestCreateAttestationTypeTestSuite(t *testing.T) { | ||
suite.Run(t, new(CreateAttestationTypeTestSuite)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"type": "object", | ||
"additionalProperties": true, | ||
"properties": { | ||
"name": {"type": "string"}, | ||
"age": {"type": "integer"} | ||
} | ||
} |