Skip to content

Commit

Permalink
feat(api): rewrite server using gorilla/mux for routing
Browse files Browse the repository at this point in the history
* refactor(api): update some endpoints path to match new lineage system
* feat(server): add CORS middleware to abstract headers modifications
* refactor(api): update DefaultVersion function to use lineage
  • Loading branch information
hbollon committed Jul 23, 2021
1 parent f41a383 commit f79f764
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 166 deletions.
80 changes: 16 additions & 64 deletions api/api.go
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/camptocamp/terraboard/compare"
"github.com/camptocamp/terraboard/db"
"github.com/camptocamp/terraboard/state"
"github.com/camptocamp/terraboard/util"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
)

Expand All @@ -27,40 +27,9 @@ func JSONError(w http.ResponseWriter, message string, err error) {
}
}

// ListStates lists States
func ListStates(w http.ResponseWriter, _ *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
states := d.ListStates()

j, err := json.Marshal(states)
if err != nil {
JSONError(w, "Failed to marshal states", err)
return
}
if _, err := io.WriteString(w, string(j)); err != nil {
log.Error(err.Error())
}
}

// ListStatesWithLineages lists distinct State paths with associated Lineages
func ListStatesWithLineages(w http.ResponseWriter, _ *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
states := d.ListStatesWithLineages()

j, err := json.Marshal(states)
if err != nil {
JSONError(w, "Failed to marshal states", err)
return
}
if _, err := io.WriteString(w, string(j)); err != nil {
log.Error(err.Error())
}
}

// ListTerraformVersionsWithCount lists Terraform versions with their associated
// counts, sorted by the 'orderBy' parameter (version by default)
func ListTerraformVersionsWithCount(w http.ResponseWriter, r *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
query := r.URL.Query()
versions, _ := d.ListTerraformVersionsWithCount(query)

Expand All @@ -76,7 +45,6 @@ func ListTerraformVersionsWithCount(w http.ResponseWriter, r *http.Request, d *d

// ListStateStats returns State information for a given path as parameter
func ListStateStats(w http.ResponseWriter, r *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
query := r.URL.Query()
states, page, total := d.ListStateStats(query)

Expand All @@ -97,18 +65,17 @@ func ListStateStats(w http.ResponseWriter, r *http.Request, d *db.Database) {

// GetState provides information on a State
func GetState(w http.ResponseWriter, r *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
lineage := util.TrimBasePath(r, "api/state/")
params := mux.Vars(r)
versionID := r.URL.Query().Get("versionid")
// var err error
// if versionID == "" {
// versionID, err = d.DefaultVersion(st)
// if err != nil {
// JSONError(w, "Failed to retrieve default version", err)
// return
// }
// }
state := d.GetState(lineage, versionID)
var err error
if versionID == "" {
versionID, err = d.DefaultVersion(params["lineage"])
if err != nil {
JSONError(w, "Failed to retrieve default version", err)
return
}
}
state := d.GetState(params["lineage"], versionID)

j, err := json.Marshal(state)
if err != nil {
Expand All @@ -122,9 +89,8 @@ func GetState(w http.ResponseWriter, r *http.Request, d *db.Database) {

// GetLineageActivity returns the activity (version history) of a Lineage
func GetLineageActivity(w http.ResponseWriter, r *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
lineage := util.TrimBasePath(r, "api/lineage/activity/")
activity := d.GetLineageActivity(lineage)
params := mux.Vars(r)
activity := d.GetLineageActivity(params["lineage"])

j, err := json.Marshal(activity)
if err != nil {
Expand All @@ -138,14 +104,13 @@ func GetLineageActivity(w http.ResponseWriter, r *http.Request, d *db.Database)

// StateCompare compares two versions ('from' and 'to') of a State
func StateCompare(w http.ResponseWriter, r *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
lineage := util.TrimBasePath(r, "api/state/compare/")
params := mux.Vars(r)
query := r.URL.Query()
fromVersion := query.Get("from")
toVersion := query.Get("to")

from := d.GetState(lineage, fromVersion)
to := d.GetState(lineage, toVersion)
from := d.GetState(params["lineage"], fromVersion)
to := d.GetState(params["lineage"], toVersion)
compare, err := compare.Compare(from, to)
if err != nil {
JSONError(w, "Failed to compare state versions", err)
Expand All @@ -164,7 +129,6 @@ func StateCompare(w http.ResponseWriter, r *http.Request, d *db.Database) {

// GetLocks returns information on locked States
func GetLocks(w http.ResponseWriter, _ *http.Request, sps []state.Provider) {
w.Header().Set("Access-Control-Allow-Origin", "*")
allLocks := make(map[string]state.LockInfo)
for _, sp := range sps {
locks, err := sp.GetLocks()
Expand All @@ -190,7 +154,6 @@ func GetLocks(w http.ResponseWriter, _ *http.Request, sps []state.Provider) {
// SearchAttribute performs a search on Resource Attributes
// by various parameters
func SearchAttribute(w http.ResponseWriter, r *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
query := r.URL.Query()
result, page, total := d.SearchAttribute(query)

Expand All @@ -212,7 +175,6 @@ func SearchAttribute(w http.ResponseWriter, r *http.Request, d *db.Database) {

// ListResourceTypes lists all Resource types
func ListResourceTypes(w http.ResponseWriter, _ *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
result, _ := d.ListResourceTypes()
j, err := json.Marshal(result)
if err != nil {
Expand All @@ -226,7 +188,6 @@ func ListResourceTypes(w http.ResponseWriter, _ *http.Request, d *db.Database) {

// ListResourceTypesWithCount lists all Resource types with their associated count
func ListResourceTypesWithCount(w http.ResponseWriter, _ *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
result, _ := d.ListResourceTypesWithCount()
j, err := json.Marshal(result)
if err != nil {
Expand All @@ -240,7 +201,6 @@ func ListResourceTypesWithCount(w http.ResponseWriter, _ *http.Request, d *db.Da

// ListResourceNames lists all Resource names
func ListResourceNames(w http.ResponseWriter, _ *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
result, _ := d.ListResourceNames()
j, err := json.Marshal(result)
if err != nil {
Expand All @@ -255,7 +215,6 @@ func ListResourceNames(w http.ResponseWriter, _ *http.Request, d *db.Database) {
// ListAttributeKeys lists all Resource Attribute Keys,
// optionally filtered by resource_type
func ListAttributeKeys(w http.ResponseWriter, r *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
resourceType := r.URL.Query().Get("resource_type")
result, _ := d.ListAttributeKeys(resourceType)
j, err := json.Marshal(result)
Expand All @@ -270,7 +229,6 @@ func ListAttributeKeys(w http.ResponseWriter, r *http.Request, d *db.Database) {

// ListTfVersions lists all Terraform versions
func ListTfVersions(w http.ResponseWriter, _ *http.Request, d *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
result, _ := d.ListTfVersions()
j, err := json.Marshal(result)
if err != nil {
Expand All @@ -284,8 +242,6 @@ func ListTfVersions(w http.ResponseWriter, _ *http.Request, d *db.Database) {

// GetUser returns information about the logged user
func GetUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")

name := r.Header.Get("X-Forwarded-User")
email := r.Header.Get("X-Forwarded-Email")

Expand All @@ -304,8 +260,6 @@ func GetUser(w http.ResponseWriter, r *http.Request) {
// SubmitPlan inserts a new Terraform plan in the database.
// /api/plans POST endpoint callback
func SubmitPlan(w http.ResponseWriter, r *http.Request, db *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")

body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Errorf("Failed to read body: %v", err)
Expand All @@ -325,7 +279,6 @@ func SubmitPlan(w http.ResponseWriter, r *http.Request, db *db.Database) {
// Sorted by most recent to oldest.
// /api/plans GET endpoint callback
func GetPlans(w http.ResponseWriter, r *http.Request, db *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
lineage := r.URL.Query().Get("lineage")
limit := r.URL.Query().Get("limit")
plans := db.GetPlans(lineage, limit)
Expand Down Expand Up @@ -357,7 +310,6 @@ func ManagePlans(w http.ResponseWriter, r *http.Request, db *db.Database) {
// Optional "&limit=X" parameter to limit requested quantity of them.
// Sorted by most recent to oldest.
func GetLineages(w http.ResponseWriter, r *http.Request, db *db.Database) {
w.Header().Set("Access-Control-Allow-Origin", "*")
limit := r.URL.Query().Get("limit")
lineages := db.GetLineages(limit)

Expand Down
61 changes: 13 additions & 48 deletions db/db.go
Expand Up @@ -429,46 +429,6 @@ func (db *Database) ListStatesVersions() (statesVersions map[string][]string) {
return
}

// ListStates returns a slice of all State paths from the Database
func (db *Database) ListStates() (states []string) {
rows, _ := db.Table("states").Select("DISTINCT path").Rows()
defer rows.Close()
for rows.Next() {
var state string
if err := rows.Scan(&state); err != nil {
log.Error(err.Error())
}
states = append(states, state)
}
return
}

// ListStatesWithLineages returns a slice of all distinct State paths with Lineage from the Database
func (db *Database) ListStatesWithLineages() (states []interface{}) {
type jsonStateLineage struct {
Path string `json:"path"`
Lineage string `json:"lineage"`
VersionID string `json:"version_id"`
}

rows, _ := db.
Table("states").
Joins("JOIN lineages ON lineages.id = states.lineage_id").
Joins("JOIN versions ON versions.id = states.version_id").
Select("DISTINCT ON(states.path) states.path, versions.version_id, lineages.value as lineage").
Rows()
defer rows.Close()

for rows.Next() {
var state jsonStateLineage
if err := rows.Scan(&state.Path, &state.VersionID, &state.Lineage); err != nil {
log.Error(err.Error())
}
states = append(states, state)
}
return
}

// ListTerraformVersionsWithCount returns a slice of maps of Terraform versions
// mapped to the count of most recent State paths using them.
// ListTerraformVersionsWithCount also takes a query with possible parameter 'orderBy'
Expand Down Expand Up @@ -513,11 +473,16 @@ func (db *Database) ListStateStats(query url.Values) (states []types.StateStat,
log.Error(err.Error())
}

offset := 0
var paginationQuery string
var params []interface{}
page = 1
if v := string(query.Get("page")); v != "" {
page, _ = strconv.Atoi(v) // TODO: err
offset = (page - 1) * pageSize
offset := (page - 1) * pageSize
params = append(params, offset)
paginationQuery = " LIMIT 20 OFFSET ?"
} else {
page = -1
}

sql := "SELECT t.path, lineages.value as lineage_value, t.serial, t.tf_version, t.version_id, t.last_modified, count(resources.*) as resource_count" +
Expand All @@ -527,10 +492,9 @@ func (db *Database) ListStateStats(query url.Values) (states []types.StateStat,
" JOIN lineages ON lineages.id = t.lineage_id" +
" GROUP BY t.path, lineages.value, t.serial, t.tf_version, t.version_id, t.last_modified" +
" ORDER BY last_modified DESC" +
" LIMIT 20" +
" OFFSET ?"
paginationQuery

db.Raw(sql, offset).Find(&states)
db.Raw(sql, params...).Find(&states)
return
}

Expand Down Expand Up @@ -716,14 +680,15 @@ func (db *Database) GetLineages(limitStr string) (lineages []types.Lineage) {

// DefaultVersion returns the detault VersionID for a given State path
// Copied and adapted from github.com/hashicorp/terraform/command/jsonstate/state.go
func (db *Database) DefaultVersion(path string) (version string, err error) {
func (db *Database) DefaultVersion(lineage string) (version string, err error) {
sqlQuery := "SELECT versions.version_id FROM" +
" (SELECT states.path, max(states.serial) as mx FROM states GROUP BY states.path) t" +
" JOIN states ON t.path = states.path AND t.mx = states.serial" +
" JOIN versions on states.version_id=versions.id" +
" WHERE states.path = ?"
" JOIN lineages on lineages.id=states.lineage_id" +
" WHERE lineages.value = ?"

row := db.Raw(sqlQuery, path).Row()
row := db.Raw(sqlQuery, lineage).Row()
err = row.Scan(&version)
return
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -11,6 +11,7 @@ require (
github.com/davecgh/go-spew v1.1.1
github.com/go-test/deep v1.0.3
github.com/google/go-cmp v0.5.5
github.com/gorilla/mux v1.8.0
github.com/hashicorp/errwrap v1.1.0
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-hclog v0.15.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -315,6 +315,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
Expand Down

0 comments on commit f79f764

Please sign in to comment.