diff --git a/.dredd/hooks/capabilities.go b/.dredd/hooks/capabilities.go index af2ca0849..b712960da 100644 --- a/.dredd/hooks/capabilities.go +++ b/.dredd/hooks/capabilities.go @@ -2,11 +2,12 @@ package main import ( "encoding/json" - "github.com/ansible-semaphore/semaphore/db" - trans "github.com/snikch/goodman/transaction" "regexp" "strconv" "strings" + + "github.com/ansible-semaphore/semaphore/db" + trans "github.com/snikch/goodman/transaction" ) // STATE @@ -18,23 +19,35 @@ var userKey *db.AccessKey var task *db.Task var schedule *db.Schedule var view *db.View +var integration *db.Integration +var integrationextractor *db.IntegrationExtractor +var integrationextractvalue *db.IntegrationExtractValue +var integrationmatch *db.IntegrationMatcher // Runtime created simple ID values for some items we need to reference in other objects var repoID int var inventoryID int var environmentID int var templateID int +var integrationID int +var integrationExtractorID int +var integrationExtractValueID int +var integrationMatchID int var capabilities = map[string][]string{ - "user": {}, - "project": {"user"}, - "repository": {"access_key"}, - "inventory": {"repository"}, - "environment": {"repository"}, - "template": {"repository", "inventory", "environment", "view"}, - "task": {"template"}, - "schedule": {"template"}, - "view": {}, + "user": {}, + "project": {"user"}, + "repository": {"access_key"}, + "inventory": {"repository"}, + "environment": {"repository"}, + "template": {"repository", "inventory", "environment", "view"}, + "task": {"template"}, + "schedule": {"template"}, + "view": {}, + "integration": {"project", "template"}, + "integrationextractor": {"integration"}, + "integrationextractvalue": {"integrationextractor"}, + "integrationmatcher": {"integrationextractor"}, } func capabilityWrapper(cap string) func(t *trans.Transaction) { @@ -131,6 +144,18 @@ func resolveCapability(caps []string, resolved []string, uid string) { templateID = res.ID case "task": task = addTask() + case "integration": + integration = addIntegration() + integrationID = integration.ID + case "integrationextractor": + integrationextractor = addIntegrationExtractor() + integrationExtractorID = integrationextractor.ID + case "integrationextractvalue": + integrationextractvalue = addIntegrationExtractValue() + integrationExtractValueID = integrationextractvalue.ID + case "integrationmatcher": + integrationmatch = addIntegrationMatcher() + integrationMatchID = integrationmatch.ID default: panic("unknown capability " + v) } @@ -157,6 +182,10 @@ var pathSubPatterns = []func() string{ func() string { return strconv.Itoa(task.ID) }, func() string { return strconv.Itoa(schedule.ID) }, func() string { return strconv.Itoa(view.ID) }, + func() string { return strconv.Itoa(integration.ID) }, + func() string { return strconv.Itoa(integrationextractor.ID) }, + func() string { return strconv.Itoa(integrationextractvalue.ID) }, + func() string { return strconv.Itoa(integrationmatch.ID) }, } // alterRequestPath with the above slice of functions @@ -165,12 +194,14 @@ func alterRequestPath(t *trans.Transaction) { exploded := make([]string, len(pathArgs)) copy(exploded, pathArgs) for k, v := range pathSubPatterns { + pos, exists := stringInSlice(strconv.Itoa(k+1), exploded) if exists { pathArgs[pos] = v() } } t.FullPath = strings.Join(pathArgs, "/") + t.Request.URI = t.FullPath } @@ -198,9 +229,24 @@ func alterRequestBody(t *trans.Transaction) { if view != nil { bodyFieldProcessor("view_id", view.ID, &request) } + + if integration != nil { + bodyFieldProcessor("integration_id", integration.ID, &request) + } + if integrationextractor != nil { + bodyFieldProcessor("extractor_id", integrationextractor.ID, &request) + } + if integrationextractvalue != nil { + bodyFieldProcessor("value_id", integrationextractvalue.ID, &request) + } + if integrationmatch != nil { + bodyFieldProcessor("matcher_id", integrationmatch.ID, &request) + } + // Inject object ID to body for PUT requests if strings.ToLower(t.Request.Method) == "put" { - putRequestPathRE := regexp.MustCompile(`/api/(?:project/\d+/)?\w+/(\d+)/?$`) + + putRequestPathRE := regexp.MustCompile(`\w+/(\d+)/?$`) m := putRequestPathRE.FindStringSubmatch(t.FullPath) if len(m) > 0 { objectID, err := strconv.Atoi(m[1]) diff --git a/.dredd/hooks/helpers.go b/.dredd/hooks/helpers.go index 43708b176..6feb37bf6 100644 --- a/.dredd/hooks/helpers.go +++ b/.dredd/hooks/helpers.go @@ -3,6 +3,10 @@ package main import ( "encoding/json" "fmt" + "math/rand" + "os" + "time" + "github.com/ansible-semaphore/semaphore/db" "github.com/ansible-semaphore/semaphore/db/bolt" "github.com/ansible-semaphore/semaphore/db/factory" @@ -10,9 +14,6 @@ import ( "github.com/ansible-semaphore/semaphore/util" "github.com/go-gorp/gorp/v3" "github.com/snikch/goodman/transaction" - "math/rand" - "os" - "time" ) // Test Runner User @@ -59,6 +60,10 @@ func truncateAll() { "project__user", "user", "project__view", + "project__integration", + "project__integration_extractor", + "project__integration_extract_value", + "project__integration_matcher", } switch store.(type) { @@ -225,6 +230,67 @@ func addTask() *db.Task { return &t } +func addIntegration() *db.Integration { + integration, err := store.CreateIntegration(db.Integration{ + ProjectID: userProject.ID, + Name: "Test Integration", + TemplateID: templateID, + }) + if err != nil { + panic(err) + } + + return &integration +} + +func addIntegrationExtractor() *db.IntegrationExtractor { + integrationextractor, err := store.CreateIntegrationExtractor(userProject.ID, db.IntegrationExtractor{ + IntegrationID: integrationID, + Name: "Integration Extractor", + }) + + if err != nil { + panic(err) + } + + return &integrationextractor +} + +func addIntegrationExtractValue() *db.IntegrationExtractValue { + integrationextractvalue, err := store.CreateIntegrationExtractValue(userProject.ID, db.IntegrationExtractValue{ + Name: "Value", + ExtractorID: integrationExtractorID, + ValueSource: db.IntegrationExtractBodyValue, + BodyDataType: db.IntegrationBodyDataJSON, + Key: "key", + Variable: "var", + }) + + if err != nil { + panic(err) + } + + return &integrationextractvalue +} + +func addIntegrationMatcher() *db.IntegrationMatcher { + integrationmatch, err := store.CreateIntegrationMatcher(userProject.ID, db.IntegrationMatcher{ + Name: "matcher", + ExtractorID: integrationExtractorID, + MatchType: "body", + Method: "equals", + BodyDataType: "json", + Key: "key", + Value: "value", + }) + + if err != nil { + panic(err) + } + + return &integrationmatch +} + // Token Handling func addToken(tok string, user int) { _, err := store.CreateAPIToken(db.APIToken{ diff --git a/.dredd/hooks/main.go b/.dredd/hooks/main.go index bb7793611..0cb09e720 100644 --- a/.dredd/hooks/main.go +++ b/.dredd/hooks/main.go @@ -58,6 +58,7 @@ func main() { defer store.Close("") addToken(expiredToken, testRunnerUser.ID) }) + h.After("user > /api/user/tokens/{api_token_id} > Expires API token > 204 > application/json", func(transaction *trans.Transaction) { dbConnect() defer store.Close("") @@ -78,6 +79,24 @@ func main() { transaction.Request.Body = "{ \"user_id\": " + strconv.Itoa(userPathTestUser.ID) + ",\"role\": \"owner\"}" }) + h.Before("project > /api/project/{project_id}/integrations > get all integrations > 200 > application/json", capabilityWrapper("integration")) + h.Before("project > /api/project/{project_id}/integrations/{integration_id} > Get Integration > 200 > application/json", capabilityWrapper("integration")) + h.Before("project > /api/project/{project_id}/integrations/{integration_id} > Update Integration > 204 > application/json", capabilityWrapper("integration")) + h.Before("project > /api/project/{project_id}/integrations/{integration_id} > Remove integration > 204 > application/json", capabilityWrapper("integration")) + + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors > Get Integration Extractors > 200 > application/json", capabilityWrapper("integrationextractor")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors > Add Integration Extractor > 201 > application/json", capabilityWrapper("integration")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors/{extractor_id} > Updates Integration extractor > 204 > application/json", capabilityWrapper("integrationextractor")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors/{extractor_id} > Removes integration extractor > 204 > application/json", capabilityWrapper("integrationextractor")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors/{extractor_id}/values > Get Integration Extracted Values linked to integration extractor > 200 > application/json", capabilityWrapper("integrationextractvalue")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors/{extractor_id}/values > Add Integration Extracted Value > 204 > application/json", capabilityWrapper("integrationextractvalue")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors/{extractor_id}/values/{extractvalue_id} > Removes integration extract value > 204 > application/json", capabilityWrapper("integrationextractvalue")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors/{extractor_id}/values > Add Integration Extracted Value > 204 > application/json", capabilityWrapper("integrationextractor")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors/{extractor_id}/values/{extractvalue_id} > Updates Integration ExtractValue > 204 > application/json", capabilityWrapper("integrationextractvalue")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors/{extractor_id}/matchers > Get Integration Matcher linked to integration extractor > 200 > application/json", capabilityWrapper("integrationextractor")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors/{extractor_id}/matchers > Add Integration Matcher > 204 > application/json", capabilityWrapper("integrationextractor")) + h.Before("integration > /api/project/{project_id}/integrations/{integration_id}/extractors/{extractor_id}/matchers/{matcher_id} > Updates Integration Matcher > 204 > application/json", capabilityWrapper("integrationmatcher")) + h.Before("project > /api/project/{project_id}/keys/{key_id} > Updates access key > 204 > application/json", capabilityWrapper("access_key")) h.Before("project > /api/project/{project_id}/keys/{key_id} > Removes access key > 204 > application/json", capabilityWrapper("access_key")) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 11559e1b3..f9f5edd58 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -57,12 +57,7 @@ jobs: with: name: semaphore - - run: "cat > config.json < config.json < config.json < config.json < 1 { + fields += "(" + values += "(" + } + + for i := 0; i < typeFieldSize; i++ { + if val.Type().Field(i).Name == "ID" { + continue + } + fields += val.Type().Field(i).Tag.Get("db") + values += "?" + args = append(args, val.Field(i)) + if i != (typeFieldSize - 1) { + fields += ", " + values += ", " + } + } + + if typeFieldSize > 1 { + fields += ")" + values += ")" + } + + return fields + " values " + values, args +} + +func AddParams(params db.RetrieveQueryParams, q *squirrel.SelectBuilder, props db.ObjectProps) { + orderDirection := "ASC" + if params.SortInverted { + orderDirection = "DESC" + } + + orderColumn := props.DefaultSortingColumn + if containsStr(props.SortableColumns, params.SortBy) { + orderColumn = params.SortBy + } + + if orderColumn != "" { + q.OrderBy("t." + orderColumn + " " + orderDirection) + } +} + +func (d *SqlDb) GetObject(props db.ObjectProps, ID int) (object interface{}, err error) { + query, args, err := squirrel.Select("t.*"). + From(props.TableName + " as t"). + Where(squirrel.Eq{"t.id": ID}). + OrderBy("t.id"). + ToSql() + + if err != nil { + return + } + err = d.selectOne(&object, query, args...) + + return +} + +func (d *SqlDb) CreateObject(props db.ObjectProps, object interface{}) (newObject interface{}, err error) { + //err = newObject.Validate() + + if err != nil { + return + } + + template, args := InsertTemplateFromType(newObject) + insertID, err := d.insert( + "id", + "insert into "+props.TableName+" "+template, args...) + + if err != nil { + return + } + + newObject = object + + v := reflect.ValueOf(newObject) + field := v.FieldByName("ID") + field.SetInt(int64(insertID)) + + return +} + +func (d *SqlDb) GetObjectsByForeignKeyQuery(props db.ObjectProps, foreignID int, foreignProps db.ObjectProps, params db.RetrieveQueryParams, objects interface{}) (err error) { + q := squirrel.Select("*"). + From(props.TableName+" as t"). + Where(foreignProps.ReferringColumnSuffix+"=?", foreignID) + + AddParams(params, &q, props) + + query, args, err := q. + OrderBy("t.id"). + ToSql() + + if err != nil { + return + } + err = d.selectOne(&objects, query, args...) + + return +} + +func (d *SqlDb) GetAllObjectsByForeignKey(props db.ObjectProps, foreignID int, foreignProps db.ObjectProps) (objects interface{}, err error) { + query, args, err := squirrel.Select("*"). + From(props.TableName+" as t"). + Where(foreignProps.ReferringColumnSuffix+"=?", foreignID). + OrderBy("t.id"). + ToSql() + + if err != nil { + return + } + + results, errQuery := d.selectAll(&objects, query, args...) + + return results, errQuery +} + +func (d *SqlDb) GetAllObjects(props db.ObjectProps) (objects interface{}, err error) { + query, args, err := squirrel.Select("*"). + From(props.TableName + " as t"). + OrderBy("t.id"). + ToSql() + + if err != nil { + return + } + var results []interface{} + results, err = d.selectAll(&objects, query, args...) + + return results, err + +} + +// Retrieve the Matchers & Values referncing `id' from WebhookExtractor +// -- +// Examples: +// referrerCollection := db.ObjectReferrers{} +// +// d.GetReferencesForForeignKey(db.ProjectProps, id, map[string]db.ObjectProps{ +// 'Templates': db.TemplateProps, +// 'Inventories': db.InventoryProps, +// 'Repositories': db.RepositoryProps +// }, &referrerCollection) +// +// // +// +// referrerCollection := db.WebhookExtractorReferrers{} +// +// d.GetReferencesForForeignKey(db.WebhookProps, id, map[string]db.ObjectProps{ +// "Matchers": db.WebhookMatcherProps, +// "Values": db.WebhookExtractValueProps +// }, &referrerCollection) +func (d *SqlDb) GetReferencesForForeignKey(objectProps db.ObjectProps, objectID int, referrerMapping map[string]db.ObjectProps, referrerCollection *interface{}) (err error) { + + for key, value := range referrerMapping { + //v := reflect.ValueOf(referrerCollection) + referrers, errRef := d.GetObjectReferences(objectProps, value, objectID) + + if errRef != nil { + return errRef + } + reflect.ValueOf(referrerCollection).FieldByName(key).Set(reflect.ValueOf(referrers)) + } + + return +} + +// Find Object Referrers for objectID based on referring column taken from referringObjectProps +// Example: +// GetObjectReferences(db.WebhookMatchers, db.WebhookExtractorProps, extractorID) +func (d *SqlDb) GetObjectReferences(objectProps db.ObjectProps, referringObjectProps db.ObjectProps, objectID int) (referringObjs []db.ObjectReferrer, err error) { + referringObjs = make([]db.ObjectReferrer, 0) + + fields, err := objectProps.GetReferringFieldsFrom(objectProps.Type) + + cond := "" + vals := []interface{}{} + + for _, f := range fields { + if cond != "" { + cond += " or " + } + + cond += f + " = ?" + + vals = append(vals, objectID) + } + + if cond == "" { + return + } + + referringObjects := reflect.New(reflect.SliceOf(referringObjectProps.Type)) + _, err = d.selectAll( + referringObjects.Interface(), + "select id, name from "+referringObjectProps.TableName+" where "+objectProps.ReferringColumnSuffix+" = ? and "+cond, + vals...) + + if err != nil { + return + } + + for i := 0; i < referringObjects.Elem().Len(); i++ { + id := int(referringObjects.Elem().Index(i).FieldByName("ID").Int()) + name := referringObjects.Elem().Index(i).FieldByName("Name").String() + referringObjs = append(referringObjs, db.ObjectReferrer{ID: id, Name: name}) + } + + return +} diff --git a/db/sql/SqlDb_test.go b/db/sql/SqlDb_test.go index c834e8681..ee57d6b96 100644 --- a/db/sql/SqlDb_test.go +++ b/db/sql/SqlDb_test.go @@ -11,4 +11,4 @@ func TestValidatePort(t *testing.T) { if q != "select * from \"test\" where id = $1, email = $2" { t.Error("invalid postgres query") } -} \ No newline at end of file +} diff --git a/db/sql/access_key.go b/db/sql/access_key.go index cbc504ecf..a9a06925b 100644 --- a/db/sql/access_key.go +++ b/db/sql/access_key.go @@ -8,7 +8,6 @@ import ( func (d *SqlDb) GetAccessKey(projectID int, accessKeyID int) (key db.AccessKey, err error) { err = d.getObject(projectID, db.AccessKeyProps, accessKeyID, &key) - return } diff --git a/db/sql/environment.go b/db/sql/environment.go index 6ad9f2fbf..b2b0dd8a3 100644 --- a/db/sql/environment.go +++ b/db/sql/environment.go @@ -4,10 +4,9 @@ import ( "github.com/ansible-semaphore/semaphore/db" ) -func (d *SqlDb) GetEnvironment(projectID int, environmentID int) (db.Environment, error) { - var environment db.Environment - err := d.getObject(projectID, db.EnvironmentProps, environmentID, &environment) - return environment, err +func (d *SqlDb) GetEnvironment(projectID int, environmentID int) (environment db.Environment, err error) { + err = d.getObject(projectID, db.EnvironmentProps, environmentID, &environment) + return } func (d *SqlDb) GetEnvironmentRefs(projectID int, environmentID int) (db.ObjectReferrers, error) { diff --git a/db/sql/event.go b/db/sql/event.go index d40f375b8..6f209255d 100644 --- a/db/sql/event.go +++ b/db/sql/event.go @@ -1,8 +1,8 @@ package sql import ( - "github.com/ansible-semaphore/semaphore/db" "github.com/Masterminds/squirrel" + "github.com/ansible-semaphore/semaphore/db" "time" ) diff --git a/db/sql/integration.go b/db/sql/integration.go new file mode 100644 index 000000000..784f4d737 --- /dev/null +++ b/db/sql/integration.go @@ -0,0 +1,418 @@ +package sql + +import ( + "github.com/Masterminds/squirrel" + "github.com/ansible-semaphore/semaphore/db" + "strings" + // fmt" + log "github.com/sirupsen/logrus" +) + +func (d *SqlDb) CreateIntegration(integration db.Integration) (newIntegration db.Integration, err error) { + err = integration.Validate() + + if err != nil { + return + } + + insertID, err := d.insert( + "id", + "insert into project__integration "+ + "(project_id, name, template_id, auth_method, auth_secret_id, auth_header) values "+ + "(?, ?, ?, ?, ?, ?)", + integration.ProjectID, + integration.Name, + integration.TemplateID, + integration.AuthMethod, + integration.AuthSecretID, + integration.AuthHeader) + + if err != nil { + return + } + + newIntegration = integration + newIntegration.ID = insertID + + return +} + +func (d *SqlDb) GetIntegrations(projectID int, params db.RetrieveQueryParams) (integrations []db.Integration, err error) { + err = d.getProjectObjects(projectID, db.IntegrationProps, params, &integrations) + return integrations, err +} + +func (d *SqlDb) GetAllIntegrations() (integrations []db.Integration, err error) { + var integrationObjects interface{} + integrationObjects, err = d.GetAllObjects(db.IntegrationProps) + integrations = integrationObjects.([]db.Integration) + return +} + +func (d *SqlDb) GetIntegration(projectID int, integrationID int) (integration db.Integration, err error) { + err = d.getObject(projectID, db.IntegrationProps, integrationID, &integration) + return +} + +func (d *SqlDb) GetIntegrationRefs(projectID int, integrationID int) (referrers db.IntegrationReferrers, err error) { + var extractorReferrer []db.ObjectReferrer + extractorReferrer, err = d.GetObjectReferences(db.IntegrationProps, db.IntegrationExtractorProps, integrationID) + referrers = db.IntegrationReferrers{ + IntegrationExtractors: extractorReferrer, + } + return +} + +func (d *SqlDb) DeleteIntegration(projectID int, integrationID int) error { + extractors, err := d.GetIntegrationExtractors(0, db.RetrieveQueryParams{}, integrationID) + + if err != nil { + return err + } + + for extractor := range extractors { + d.DeleteIntegrationExtractor(0, extractors[extractor].ID, integrationID) + } + return d.deleteObject(projectID, db.IntegrationProps, integrationID) +} + +func (d *SqlDb) UpdateIntegration(integration db.Integration) error { + err := integration.Validate() + + if err != nil { + return err + } + + _, err = d.exec( + "update project__integration set `name`=?, template_id=?, auth_method=?, auth_secret_id=?, auth_header=? where `id`=?", + integration.Name, + integration.TemplateID, + integration.AuthMethod, + integration.AuthSecretID, + integration.AuthHeader, + integration.ID) + + return err +} + +func (d *SqlDb) CreateIntegrationExtractor(projectID int, integrationExtractor db.IntegrationExtractor) (newIntegrationExtractor db.IntegrationExtractor, err error) { + err = integrationExtractor.Validate() + + if err != nil { + return + } + + insertID, err := d.insert( + "id", + "insert into project__integration_extractor (`name`, integration_id) values (?, ?)", + integrationExtractor.Name, + integrationExtractor.IntegrationID) + + if err != nil { + return + } + + newIntegrationExtractor = integrationExtractor + newIntegrationExtractor.ID = insertID + + return +} + +func (d *SqlDb) GetIntegrationExtractor(projectID int, extractorID int, integrationID int) (extractor db.IntegrationExtractor, err error) { + query, args, err := squirrel.Select("e.*"). + From("project__integration_extractor as e"). + Where(squirrel.And{ + squirrel.Eq{"integration_id": integrationID}, + squirrel.Eq{"id": extractorID}, + }). + OrderBy("e.name"). + ToSql() + + if err != nil { + return + } + + err = d.selectOne(&extractor, query, args...) + + return extractor, err +} + +func (d *SqlDb) GetIntegrationExtractors(projectID int, params db.RetrieveQueryParams, integrationID int) ([]db.IntegrationExtractor, error) { + var extractors []db.IntegrationExtractor + err := d.getObjectsByReferrer(integrationID, db.IntegrationProps, db.IntegrationExtractorProps, params, &extractors) + + return extractors, err +} + +func (d *SqlDb) GetIntegrationExtractorRefs(projectID int, extractorID int, integrationID int) (refs db.IntegrationExtractorReferrers, err error) { + refs.IntegrationMatchers, err = d.GetObjectReferences(db.IntegrationExtractorProps, db.IntegrationMatcherProps, extractorID) + refs.IntegrationExtractValues, err = d.GetObjectReferences(db.IntegrationExtractorProps, db.IntegrationExtractValueProps, extractorID) + + return +} + +func (d *SqlDb) GetIntegrationExtractValuesByExtractorID(extractorID int) (values []db.IntegrationExtractValue, err error) { + var sqlError error + query, args, sqlError := squirrel.Select("v.*"). + From("project__integration_extract_value as v"). + Where(squirrel.Eq{"extractor_id": extractorID}). + OrderBy("v.id"). + ToSql() + + if sqlError != nil { + return []db.IntegrationExtractValue{}, sqlError + } + + err = d.selectOne(&values, query, args...) + + return values, err +} + +func (d *SqlDb) GetIntegrationMatchersByExtractorID(extractorID int) (matchers []db.IntegrationMatcher, err error) { + var sqlError error + query, args, sqlError := squirrel.Select("m.*"). + From("project__integration_matcher as m"). + Where(squirrel.Eq{"extractor_id": extractorID}). + OrderBy("m.id"). + ToSql() + + if sqlError != nil { + return []db.IntegrationMatcher{}, sqlError + } + + err = d.selectOne(&matchers, query, args...) + + return matchers, err +} + +func (d *SqlDb) DeleteIntegrationExtractor(projectID int, extractorID int, integrationID int) error { + values, err := d.GetIntegrationExtractValuesByExtractorID(extractorID) + if err != nil && !strings.Contains(err.Error(), "no rows in result set") { + return err + } + + for value := range values { + + err = d.DeleteIntegrationExtractValue(0, values[value].ID, extractorID) + if err != nil && !strings.Contains(err.Error(), "no rows in result set") { + log.Error(err) + return err + } + } + + matchers, errExtractor := d.GetIntegrationMatchersByExtractorID(extractorID) + if errExtractor != nil && !strings.Contains(errExtractor.Error(), "no rows in result set") { + log.Error(errExtractor) + return errExtractor + } + + for matcher := range matchers { + err = d.DeleteIntegrationMatcher(0, matchers[matcher].ID, extractorID) + if err != nil && !strings.Contains(err.Error(), "no rows in result set") { + log.Error(err) + return err + } + } + + return d.deleteObjectByReferencedID(integrationID, db.IntegrationProps, db.IntegrationExtractorProps, extractorID) +} + +func (d *SqlDb) UpdateIntegrationExtractor(projectID int, integrationExtractor db.IntegrationExtractor) error { + err := integrationExtractor.Validate() + + if err != nil { + return err + } + + _, err = d.exec( + "update project__integration_extractor set name=? where id=?", + integrationExtractor.Name, + integrationExtractor.ID) + + return err +} + +func (d *SqlDb) CreateIntegrationExtractValue(projectId int, value db.IntegrationExtractValue) (newValue db.IntegrationExtractValue, err error) { + err = value.Validate() + + if err != nil { + return + } + + insertID, err := d.insert("id", + "insert into project__integration_extract_value "+ + "(value_source, body_data_type, `key`, `variable`, `name`, extractor_id) values "+ + "(?, ?, ?, ?, ?, ?)", + value.ValueSource, + value.BodyDataType, + value.Key, + value.Variable, + value.Name, + value.ExtractorID) + + if err != nil { + return + } + + newValue = value + newValue.ID = insertID + + return +} + +func (d *SqlDb) GetIntegrationExtractValues(projectID int, params db.RetrieveQueryParams, extractorID int) ([]db.IntegrationExtractValue, error) { + var values []db.IntegrationExtractValue + err := d.getObjectsByReferrer(extractorID, db.IntegrationExtractorProps, db.IntegrationExtractValueProps, params, &values) + return values, err +} + +func (d *SqlDb) GetAllIntegrationExtractValues() (values []db.IntegrationExtractValue, err error) { + var valueObjects interface{} + valueObjects, err = d.GetAllObjects(db.IntegrationExtractValueProps) + values = valueObjects.([]db.IntegrationExtractValue) + return +} + +func (d *SqlDb) GetIntegrationExtractValue(projectID int, valueID int, extractorID int) (value db.IntegrationExtractValue, err error) { + query, args, err := squirrel.Select("v.*"). + From("project__integration_extract_value as v"). + Where(squirrel.Eq{"id": valueID}). + OrderBy("v.id"). + ToSql() + + if err != nil { + return + } + + err = d.selectOne(&value, query, args...) + + return value, err +} + +func (d *SqlDb) GetIntegrationExtractValueRefs(projectID int, valueID int, extractorID int) (refs db.IntegrationExtractorChildReferrers, err error) { + refs.IntegrationExtractors, err = d.GetObjectReferences(db.IntegrationExtractorProps, db.IntegrationExtractValueProps, extractorID) + return +} + +func (d *SqlDb) DeleteIntegrationExtractValue(projectID int, valueID int, extractorID int) error { + return d.deleteObjectByReferencedID(extractorID, db.IntegrationExtractorProps, db.IntegrationExtractValueProps, valueID) +} + +func (d *SqlDb) UpdateIntegrationExtractValue(projectID int, integrationExtractValue db.IntegrationExtractValue) error { + err := integrationExtractValue.Validate() + + if err != nil { + return err + } + + _, err = d.exec( + "update project__integration_extract_value set value_source=?, body_data_type=?, `key`=?, `variable`=?, `name`=? where `id`=?", + integrationExtractValue.ValueSource, + integrationExtractValue.BodyDataType, + integrationExtractValue.Key, + integrationExtractValue.Variable, + integrationExtractValue.Name, + integrationExtractValue.ID) + + return err +} + +func (d *SqlDb) CreateIntegrationMatcher(projectID int, matcher db.IntegrationMatcher) (newMatcher db.IntegrationMatcher, err error) { + err = matcher.Validate() + + if err != nil { + return + } + + insertID, err := d.insert( + "id", + "insert into project__integration_matcher "+ + "(match_type, `method`, body_data_type, `key`, `value`, extractor_id, `name`) values "+ + "(?, ?, ?, ?, ?, ?, ?)", + matcher.MatchType, + matcher.Method, + matcher.BodyDataType, + matcher.Key, + matcher.Value, + matcher.ExtractorID, + matcher.Name) + + if err != nil { + return + } + + newMatcher = matcher + newMatcher.ID = insertID + + return +} + +func (d *SqlDb) GetIntegrationMatchers(projectID int, params db.RetrieveQueryParams, extractorID int) (matchers []db.IntegrationMatcher, err error) { + query, args, err := squirrel.Select("m.*"). + From("project__integration_matcher as m"). + Where(squirrel.Eq{"extractor_id": extractorID}). + OrderBy("m.id"). + ToSql() + + if err != nil { + return + } + + _, err = d.selectAll(&matchers, query, args...) + + return +} + +func (d *SqlDb) GetAllIntegrationMatchers() (matchers []db.IntegrationMatcher, err error) { + var matcherObjects interface{} + matcherObjects, err = d.GetAllObjects(db.IntegrationMatcherProps) + matchers = matcherObjects.([]db.IntegrationMatcher) + + return +} + +func (d *SqlDb) GetIntegrationMatcher(projectID int, matcherID int, extractorID int) (matcher db.IntegrationMatcher, err error) { + query, args, err := squirrel.Select("m.*"). + From("project__integration_matcher as m"). + Where(squirrel.Eq{"id": matcherID}). + OrderBy("m.id"). + ToSql() + + if err != nil { + return + } + + err = d.selectOne(&matcher, query, args...) + + return matcher, err +} + +func (d *SqlDb) GetIntegrationMatcherRefs(projectID int, matcherID int, extractorID int) (refs db.IntegrationExtractorChildReferrers, err error) { + refs.IntegrationExtractors, err = d.GetObjectReferences(db.IntegrationExtractorProps, db.IntegrationMatcherProps, matcherID) + + return +} + +func (d *SqlDb) DeleteIntegrationMatcher(projectID int, matcherID int, extractorID int) error { + return d.deleteObjectByReferencedID(extractorID, db.IntegrationExtractorProps, db.IntegrationMatcherProps, matcherID) +} + +func (d *SqlDb) UpdateIntegrationMatcher(projectID int, integrationMatcher db.IntegrationMatcher) error { + err := integrationMatcher.Validate() + + if err != nil { + return err + } + + _, err = d.exec( + "update project__integration_matcher set match_type=?, `method`=?, body_data_type=?, `key`=?, `value`=?, `name`=? where `id`=?", + integrationMatcher.MatchType, + integrationMatcher.Method, + integrationMatcher.BodyDataType, + integrationMatcher.Key, + integrationMatcher.Value, + integrationMatcher.Name, + integrationMatcher.ID) + + return err +} diff --git a/db/sql/migration.go b/db/sql/migration.go index 8139dafa0..51e08eb1f 100644 --- a/db/sql/migration.go +++ b/db/sql/migration.go @@ -2,13 +2,13 @@ package sql import ( "fmt" + "github.com/go-gorp/gorp/v3" "path" "regexp" "strings" "time" "github.com/ansible-semaphore/semaphore/db" - "github.com/go-gorp/gorp/v3" log "github.com/sirupsen/logrus" ) diff --git a/db/sql/migrations/v2.9.60.sql b/db/sql/migrations/v2.9.60.sql new file mode 100644 index 000000000..19fde627a --- /dev/null +++ b/db/sql/migrations/v2.9.60.sql @@ -0,0 +1,46 @@ +create table project__integration ( + `id` integer primary key autoincrement, + `name` varchar(255) not null, + `project_id` int not null, + `template_id` int not null, + `auth_method` varchar(15) not null default 'none', + `auth_secret_id` int, + `auth_header` varchar(255), + + foreign key (`project_id`) references project(`id`) on delete cascade, + foreign key (`template_id`) references project__template(`id`) on delete cascade, + foreign key (`auth_secret_id`) references access_key(`id`) on delete set null +); + +create table project__integration_extractor ( + `id` integer primary key autoincrement, + `name` varchar(255) not null, + `integration_id` int not null, + + foreign key (`integration_id`) references project__integration(`id`) on delete cascade +); + +create table project__integration_extract_value ( + `id` integer primary key autoincrement, + `name` varchar(255) not null, + `extractor_id` int not null, + `value_source` varchar(255) not null, + `body_data_type` varchar(255) null, + `key` varchar(255) null, + `variable` varchar(255) null, + + foreign key (`extractor_id`) references project__integration_extractor(`id`) on delete cascade +); + +create table project__integration_matcher ( + `id` integer primary key autoincrement, + `name` varchar(255) not null, + `extractor_id` int not null, + `match_type` varchar(255) null, + `method` varchar(255) null, + `body_data_type` varchar(255) null, + `key` varchar(510) null, + `value` varchar(510) null, + + foreign key (`extractor_id`) references project__integration_extractor(`id`) on delete cascade +); diff --git a/go.mod b/go.mod index a248317a2..7a6e1fe8a 100644 --- a/go.mod +++ b/go.mod @@ -21,8 +21,10 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa github.com/spf13/cobra v1.8.0 + github.com/thedevsaddam/gojsonq/v2 v2.5.2 go.etcd.io/bbolt v1.3.9 golang.org/x/crypto v0.20.0 + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/oauth2 v0.17.0 ) diff --git a/go.sum b/go.sum index 09f4ca3a1..92608da51 100644 --- a/go.sum +++ b/go.sum @@ -12,7 +12,9 @@ github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azu github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= @@ -26,11 +28,13 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -38,6 +42,7 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmS github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= @@ -58,11 +63,13 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -84,9 +91,11 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -94,7 +103,9 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -102,6 +113,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -128,6 +140,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0= +github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -142,6 +157,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= @@ -162,6 +179,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -217,6 +235,7 @@ google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/web/src/App.vue b/web/src/App.vue index e31db4319..556c9fb6b 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -260,6 +260,16 @@ + + + mdi-connection + + + + Integrations + + + mdi-account-multiple diff --git a/web/src/components/IntegrationExtractValueForm.vue b/web/src/components/IntegrationExtractValueForm.vue new file mode 100644 index 000000000..e19349410 --- /dev/null +++ b/web/src/components/IntegrationExtractValueForm.vue @@ -0,0 +1,127 @@ + + diff --git a/web/src/components/IntegrationExtractorBase.js b/web/src/components/IntegrationExtractorBase.js new file mode 100644 index 000000000..b767075f2 --- /dev/null +++ b/web/src/components/IntegrationExtractorBase.js @@ -0,0 +1,5 @@ +export default { + props: { + extractorId: Number, + }, +}; diff --git a/web/src/components/IntegrationExtractorChildValueFormBase.js b/web/src/components/IntegrationExtractorChildValueFormBase.js new file mode 100644 index 000000000..e226b20ac --- /dev/null +++ b/web/src/components/IntegrationExtractorChildValueFormBase.js @@ -0,0 +1,60 @@ +import axios from 'axios'; +import { getErrorMessage } from '@/lib/error'; + +export default { + props: { + integrationId: [Number, String], + extractorId: [Number, String], + }, + methods: { + /** + * Saves or creates item via API. + * @returns {Promise} null if validation didn't pass or user data if user saved. + */ + async save() { + this.formError = null; + + if (!this.$refs.form.validate()) { + this.$emit('error', {}); + return null; + } + + this.formSaving = true; + let item; + + try { + await this.beforeSave(); + + item = (await axios({ + method: this.isNew ? 'post' : 'put', + url: this.isNew + ? this.getItemsUrl() + : this.getSingleItemUrl(), + responseType: 'json', + data: { + ...this.item, + integration_id: this.integrationId, + extractor_id: this.extractorId, + }, + ...(this.getRequestOptions()), + })).data; + + await this.afterSave(item); + + this.$emit('save', { + item: item || this.item, + action: this.isNew ? 'new' : 'edit', + }); + } catch (err) { + this.formError = getErrorMessage(err); + this.$emit('error', { + message: this.formError, + }); + } finally { + this.formSaving = false; + } + + return item || this.item; + }, + }, +}; diff --git a/web/src/components/IntegrationExtractorForm.vue b/web/src/components/IntegrationExtractorForm.vue new file mode 100644 index 000000000..29331731d --- /dev/null +++ b/web/src/components/IntegrationExtractorForm.vue @@ -0,0 +1,43 @@ + + diff --git a/web/src/components/IntegrationExtractorFormBase.js b/web/src/components/IntegrationExtractorFormBase.js new file mode 100644 index 000000000..10fc65e5b --- /dev/null +++ b/web/src/components/IntegrationExtractorFormBase.js @@ -0,0 +1,59 @@ +import axios from 'axios'; +import { getErrorMessage } from '@/lib/error'; + +export default { + props: { + integrationId: [Number, String], + }, + methods: { + /** + * Saves or creates item via API. + * @returns {Promise} null if validation didn't pass or user data if user saved. + */ + async save() { + this.formError = null; + + if (!this.$refs.form.validate()) { + this.$emit('error', {}); + return null; + } + + this.formSaving = true; + let item; + + try { + await this.beforeSave(); + + item = (await axios({ + method: this.isNew ? 'post' : 'put', + url: this.isNew + ? this.getItemsUrl() + : this.getSingleItemUrl(), + responseType: 'json', + data: { + ...this.item, + project_id: this.$route.params.projectId, + integration_id: this.integrationId, + }, + ...(this.getRequestOptions()), + })).data; + + await this.afterSave(item); + + this.$emit('save', { + item: item || this.item, + action: this.isNew ? 'new' : 'edit', + }); + } catch (err) { + this.formError = getErrorMessage(err); + this.$emit('error', { + message: this.formError, + }); + } finally { + this.formSaving = false; + } + + return item || this.item; + }, + }, +}; diff --git a/web/src/components/IntegrationExtractorRefsView.vue b/web/src/components/IntegrationExtractorRefsView.vue new file mode 100644 index 000000000..4ae10428b --- /dev/null +++ b/web/src/components/IntegrationExtractorRefsView.vue @@ -0,0 +1,26 @@ + diff --git a/web/src/components/IntegrationExtractorsBase.js b/web/src/components/IntegrationExtractorsBase.js new file mode 100644 index 000000000..347f11468 --- /dev/null +++ b/web/src/components/IntegrationExtractorsBase.js @@ -0,0 +1,6 @@ +export default { + props: { + integrationId: Number, + projectId: Number, + }, +}; diff --git a/web/src/components/IntegrationForm.vue b/web/src/components/IntegrationForm.vue new file mode 100644 index 000000000..ff7288f91 --- /dev/null +++ b/web/src/components/IntegrationForm.vue @@ -0,0 +1,64 @@ + + diff --git a/web/src/components/IntegrationMatcherForm.vue b/web/src/components/IntegrationMatcherForm.vue new file mode 100644 index 000000000..cd16e7350 --- /dev/null +++ b/web/src/components/IntegrationMatcherForm.vue @@ -0,0 +1,148 @@ + + diff --git a/web/src/components/IntegrationRefsView.vue b/web/src/components/IntegrationRefsView.vue new file mode 100644 index 000000000..d38802159 --- /dev/null +++ b/web/src/components/IntegrationRefsView.vue @@ -0,0 +1,26 @@ + diff --git a/web/src/lib/constants.js b/web/src/lib/constants.js index 6cf9ed825..f564fae85 100644 --- a/web/src/lib/constants.js +++ b/web/src/lib/constants.js @@ -36,3 +36,39 @@ export const USER_ROLES = [{ slug: 'guest', title: 'Guest', }]; + +export const MATCHER_TYPE_TITLES = { + '': 'Matcher', + body: 'Body', + header: 'Header', +}; + +export const MATCHER_TYPE_ICONS = { + '': 'Matcher', + body: 'mdi-page-layout-body', + header: 'mdi-web', +}; + +export const EXTRACT_VALUE_TYPE_TITLES = { + '': 'ExtractValue', + body: 'Body', + header: 'Header', +}; + +export const EXTRACT_VALUE_TYPE_ICONS = { + '': 'ExtractValue', + body: 'mdi-page-layout-body', + header: 'mdi-web', +}; + +export const EXTRACT_VALUE_BODY_DATA_TYPE_TITLES = { + '': 'BodyDataType', + json: 'JSON', + str: 'String', +}; + +export const EXTRACT_VALUE_BODY_DATA_TYPE_ICONS = { + '': 'BodyDataType', + json: 'mdi-code-json', + str: 'mdi-text', +}; diff --git a/web/src/router/index.js b/web/src/router/index.js index 95e31f1ba..d0791ec19 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -13,6 +13,11 @@ import Team from '../views/project/Team.vue'; import Users from '../views/Users.vue'; import Auth from '../views/Auth.vue'; import New from '../views/project/New.vue'; +import Integrations from '../views/project/Integrations.vue'; + +import IntegrationExtractors from '../views/project/IntegrationExtractors.vue'; + +import IntegrationExtractor from '../views/project/IntegrationExtractor.vue'; Vue.use(VueRouter); @@ -65,6 +70,18 @@ const routes = [ path: '/project/:projectId/inventory', component: Inventory, }, + { + path: '/project/:projectId/integrations', + component: Integrations, + }, + { + path: '/project/:projectId/integration/:integrationId', + component: IntegrationExtractors, + }, + { + path: '/project/:projectId/integration/:integrationId/extractor/:extractorId', + component: IntegrationExtractor, + }, { path: '/project/:projectId/repositories', component: Repositories, diff --git a/web/src/views/project/IntegrationExtractValue.vue b/web/src/views/project/IntegrationExtractValue.vue new file mode 100644 index 000000000..24d7eaa7e --- /dev/null +++ b/web/src/views/project/IntegrationExtractValue.vue @@ -0,0 +1,171 @@ + + diff --git a/web/src/views/project/IntegrationExtractor.vue b/web/src/views/project/IntegrationExtractor.vue new file mode 100644 index 000000000..f58495fe3 --- /dev/null +++ b/web/src/views/project/IntegrationExtractor.vue @@ -0,0 +1,39 @@ + + diff --git a/web/src/views/project/IntegrationExtractorCrumb.vue b/web/src/views/project/IntegrationExtractorCrumb.vue new file mode 100644 index 000000000..55eb9000f --- /dev/null +++ b/web/src/views/project/IntegrationExtractorCrumb.vue @@ -0,0 +1,90 @@ + + diff --git a/web/src/views/project/IntegrationExtractors.vue b/web/src/views/project/IntegrationExtractors.vue new file mode 100644 index 000000000..e1d66c7b2 --- /dev/null +++ b/web/src/views/project/IntegrationExtractors.vue @@ -0,0 +1,164 @@ + + diff --git a/web/src/views/project/IntegrationMatcher.vue b/web/src/views/project/IntegrationMatcher.vue new file mode 100644 index 000000000..a75fce00a --- /dev/null +++ b/web/src/views/project/IntegrationMatcher.vue @@ -0,0 +1,177 @@ + + diff --git a/web/src/views/project/Integrations.vue b/web/src/views/project/Integrations.vue new file mode 100644 index 000000000..27d13da28 --- /dev/null +++ b/web/src/views/project/Integrations.vue @@ -0,0 +1,146 @@ + +