Skip to content

Commit

Permalink
validate docs (#4)
Browse files Browse the repository at this point in the history
* validationPOST: explore a first validation step

* validationPOST: woopsy on LICENSE

* validationPOST: set isDebug from env

* validationPOST: bump timeouts of small commands for Windows

* add shellcheck on quickinstall script

* docs validation

* validationPOST: require bash for declare-p things

* validationPOST: fmt

* validationPOST: less awkward bash requirement

* validationPOST: naming things is hard
  • Loading branch information
fenollp authored Nov 8, 2017
1 parent 9d9b332 commit 8ccebb3
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ vendor/
# End of https://www.gitignore.io/api/go

testman
testman-*
schemas.go
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ before_install:
- set -o pipefail
- go get github.com/mitchellh/gox

addons:
apt:
sources:
- debian-sid # Grab ShellCheck from the Debian repo
packages:
- shellcheck

script:
- go generate
- go get -t -v ./...
Expand All @@ -19,6 +26,9 @@ script:
- ./testman-linux_amd64 -h | grep testman
- ./testman-linux_amd64 --version | grep testman
- ls -lha

after_script:
- shellcheck misc/latest.sh
- set +e

deploy:
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2016 Postdot Technologies, Inc
Copyright 2017 EURL Twang

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ all: $(filter-out schemas.go,$(wildcard *.go)) $(wildcard misc/*.json)
go build -o $(EXE)

debug: all
./$(EXE) test --slow
DEBUG=1 ./$(EXE) test --slow
2 changes: 1 addition & 1 deletion cmd_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ func (cmd reqCmd) Exec(cfg *ymlCfg) []byte {
} else {
rep, err = json.Marshal(ko)
}

if err != nil {
log.Fatal("!encode: ", err)
}

return rep
}

Expand Down
10 changes: 3 additions & 7 deletions cmd_simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func uniquePath() string {
}

func snapEnv(envSerializedPath string) {
cmdTimeout := 10 * time.Millisecond
cmdTimeout := 200 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(), cmdTimeout)
defer cancel()

Expand All @@ -109,7 +109,7 @@ func snapEnv(envSerializedPath string) {

//FIXME: make this faster! parse the .env file?
func readEnv(envSerializedPath, envVar string) string {
cmdTimeout := 100 * time.Millisecond // has to be >10ms...
cmdTimeout := 200 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(), cmdTimeout)
defer cancel()

Expand All @@ -126,11 +126,7 @@ func readEnv(envSerializedPath, envVar string) string {
}

func shell() string {
SHELL := os.Getenv("SHELL")
if "" == SHELL {
log.Fatal("$SHELL is unset")
}
return SHELL
return "/bin/bash"
}

func unstacheEnv(envVar string, options *raymond.Options) raymond.SafeString {
Expand Down
12 changes: 2 additions & 10 deletions dialogue.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package main

import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"os"
"time"
// "archive/tar" FIXME: tar + gz then upload read only conf

"gopkg.in/yaml.v2"
)
Expand All @@ -34,13 +32,8 @@ type ymlCfg struct {
func initDialogue(apiKey string) (*ymlCfg, aCmd) {
yml := readYAML(localYML)

// Has to be a string cause []byte gets base64-encoded
fixtures := map[string]string{localYML: string(yml)}
payload, err := json.Marshal(fixtures)
if err != nil {
log.Fatal(err)
}
cmdJSON, authToken := initPUT(apiKey, payload)
validationJSON := validateDocs(apiKey, yml)
cmdJSON, authToken := initPUT(apiKey, validationJSON)
cmd := unmarshalCmd(cmdJSON)

var ymlConf struct {
Expand Down Expand Up @@ -97,7 +90,6 @@ func readYAML(path string) []byte {
}

func initPUT(apiKey string, JSON []byte) ([]byte, string) {
var r *http.Request
r, err := http.NewRequest(http.MethodPut, initURL, bytes.NewBuffer(JSON))
if err != nil {
log.Fatal("!initPUT: ", err)
Expand Down
22 changes: 17 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,29 @@ import (
//go:generate go run misc/include_jsons.go

const (
pkgVersion = "0.2.0"
pkgVersion = "0.3.0"
pkgTitle = "testman/" + pkgVersion
isDebug = false
)

var (
isDebug bool
apiRoot string
initURL string
nextURL string
docsURL string
)

func init() {
log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.LUTC)

isDebug = "1" == os.Getenv("DEBUG")

if isDebug {
apiRoot = "http://localhost:1042" //FIXME use HTTPS
apiRoot = "http://localhost:1042"
docsURL = "http://localhost:2042/blob"
} else {
apiRoot = "https://testman.coveredci.com"
apiRoot = "https://test.coveredci.com"
docsURL = "https://lint.coveredci.com/blob"
}
initURL = apiRoot + "/1/init"
nextURL = apiRoot + "/1/next"
Expand Down Expand Up @@ -65,7 +70,14 @@ func main() {
}
}

if _, err := os.Stat(shell()); os.IsNotExist(err) {
log.Fatal(shell() + " is required")
}

apiKey := os.Getenv("COVEREDCI_API_KEY")
if isDebug {
apiKey = "42"
}
if apiKey == "" {
log.Fatal("$COVEREDCI_API_KEY is unset")
}
Expand All @@ -87,7 +99,7 @@ func main() {
}

func ensureDeleted(path string) {
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
if err := os.Remove(path); err != nil && os.IsExist(err) {
log.Fatal(err)
}
}
10 changes: 5 additions & 5 deletions misc/latest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ target_path=/usr/local/bin
case :$PATH: in
*:$target_path:*) ;;
*)
echo $target_path' is not in your $PATH'
echo $target_path' is not in your PATH'
exit 2
;;
esac

echo Looking for latest tag of $slug
latest_tag_url=$(curl --silent --location --output /dev/null --write-out '%{url_effective}' https://github.com/$slug/releases/latest)
latest_tag=$(basename $latest_tag_url)
echo Latest tag: $latest_tag
latest_tag=$(basename "$latest_tag_url")
echo "Latest tag: $latest_tag"

exe=''
case $(uname) in
Expand All @@ -29,9 +29,9 @@ case $(uname) in
;;
esac

echo Downloading v$latest_tag of $exe
echo "Downloading v$latest_tag of $exe"
tmp=/tmp/testman
curl --silent --location --output $tmp https://github.com/$slug/releases/download/$latest_tag/$exe
curl --silent --location --output $tmp "https://github.com/$slug/releases/download/$latest_tag/$exe"
chmod +x $tmp
mv --verbose $tmp $target_path/

Expand Down
116 changes: 116 additions & 0 deletions validate_docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"

"gopkg.in/yaml.v2"
)

func validateDocs(apiKey string, yml []byte) []byte {
docs := struct {
V uint `json:"v"`
Blobs map[string]string `json:"blobs"`
}{
V: 1,
Blobs: blobs(yml),
}

payload, err := json.Marshal(docs)
if err != nil {
log.Fatal(err)
}

return validationPOST(apiKey, payload)
}

func validationPOST(apiKey string, JSON []byte) []byte {
r, err := http.NewRequest(http.MethodPost, docsURL, bytes.NewBuffer(JSON))
if err != nil {
log.Fatal(err)
}

r.Header.Set("Content-Type", mimeJSON)
r.Header.Set("Accept", mimeJSON)
r.Header.Set("Accept-Encoding", "gzip, deflate, br")
r.Header.Set(xAPIKeyHeader, apiKey)
client := &http.Client{}

start := time.Now()
resp, err := client.Do(r)
us := uint64(time.Since(start) / time.Microsecond)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal("!read body: ", err)
}
log.Printf("🡱 %vμs POST %s\n 🡱 %s\n 🡳 %s\n", us, docsURL, JSON, body)

if resp.StatusCode == 400 {
reportValidationErrors(body)
log.Fatal("Documentation validation failed")
}

if resp.StatusCode != 200 {
log.Fatal("!200: ", resp.Status)
}

var validated struct {
V uint `json:"v"`
Token string `json:"token"`
}
if err := json.Unmarshal(body, &validated); err != nil {
log.Fatal(err)
}
if validated.Token == "" {
log.Fatal("Could not acquire a validation token")
}

return body
}

func reportValidationErrors(errors []byte) {
fmt.Println("Validation errors:")

var out bytes.Buffer
err := json.Indent(&out, errors, "", " ")
if err != nil {
fmt.Println(string(errors))
}

fmt.Println(out.String())
}

func blobs(yml []byte) map[string]string {
blobs := map[string]string{localYML: string(yml)}

var ymlConfPartial struct {
Doc struct {
File string `yaml:"file"`
} `yaml:"documentation"`
}
// YML is not yet validated, so ignore errors
yaml.Unmarshal(yml, &ymlConfPartial)

filePath := ymlConfPartial.Doc.File
if "" == filePath {
return blobs
}

fileData, err := ioutil.ReadFile(filePath)
if err != nil {
log.Fatal(err)
}
blobs[filePath] = string(fileData)

return blobs
}

0 comments on commit 8ccebb3

Please sign in to comment.