<commit_before>// Copyright 2015 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// recorder is allows to capture request/response pairs via a
// reverse proxy and generate tests for these pairs.
package recorder

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"net/url"
	"os"
	"path"
	"regexp"
	"strings"
	"time"

	"github.com/vdobler/ht/ht"
	"github.com/vdobler/ht/internal/json5"
)

// Events is the global list of recorded events.
var Events []Event

// Event is a request/response pair
type Event struct {
	Request   *http.Request              // The request
	Response  *httptest.ResponseRecorder // The recorded response
	Body      []byte                     // The captured body
	Timestamp time.Time                  // Timestamp when caputred
	Name      string                     // Used during dumping
}

type Options struct {
	// Disarm is the time span after a captured request/response pair
	// in which the capturing is disarmed.
	Disarm time.Duration

	IgnoredContentType *regexp.Regexp
	IgnoredPath        *regexp.Regexp
}

func (o Options) ignore(e Event) bool {
	if o.IgnoredPath != nil && o.IgnoredPath.MatchString(e.Request.URL.Path) {
		log.Println("Ignoring path", e.Request.URL.Path)
		return true
	}
	if o.IgnoredContentType != nil &&
		o.IgnoredContentType.MatchString(e.Response.HeaderMap.Get("Content-Type")) {
		log.Println("Ignoring content type ", e.Response.HeaderMap.Get("Content-Type"))
		return true
	}
	return false
}

var (
	remoteHost string
)

// StartReverseProxy listens on the local port and forwards request to remote
// while capturing the request/response pairs.
func StartReverseProxy(port string, remote *url.URL, opts Options) error {
	remoteHost = remote.Host
	requests := make(chan Event, 10)
	go process(requests, opts)

	proxy := httputil.NewSingleHostReverseProxy(remote)
	http.HandleFunc("/", handler(proxy, requests))
	log.Printf("Staring reverse proxying from localhost:%s to %s", port, remote.String())
	return http.ListenAndServe(port, nil)
}

func handler(p *httputil.ReverseProxy, requests chan Event) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		rr := httptest.NewRecorder()
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			panic(err.Error()) // Harsh but what else?
		}
		r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
		p.ServeHTTP(rr, r)
		requests <- Event{
			Request:   r,
			Response:  rr,
			Body:      body,
			Timestamp: time.Now()}
		for h, v := range rr.HeaderMap {
			w.Header()[h] = v
		}
		w.WriteHeader(rr.Code)
		w.Write(rr.Body.Bytes())
	}
}

func process(requests chan Event, opts Options) {
	log.Println("Started processing")
	last := time.Now()
	for e := range requests {
		delta := e.Timestamp.Sub(last)
		if delta < opts.Disarm {
			continue
		}
		if opts.ignore(e) {
			continue
		}
		last = e.Timestamp
		e.Name = fmt.Sprintf("Event %d", len(Events)+1)
		Events = append(Events, e)
		log.Println("Recorded", e.Request.Method, e.Request.URL, " --> ", e.Response.Code)
	}
}

// Test is a reduced version of ht.Test suitable for serialization to JSON5.
type Test struct {
	Name        string
	Description string   `json:",omitempty"`
	BasedOn     []string `json:",omitempty"`
	Request     ht.Request
	Checks      ht.CheckList `json:",omitempty"`
}

// Suite is a reduced version of ht.Suite suitable to serialization to JSON5.
type Suite struct {
	Name        string
	Description string `json:",omitempty"`
	Tests       []string
	Variables   map[string]string
}

// DumpEvents writes events to directory, it extracts common headers.
func DumpEvents(events []Event, directory string, suitename string) error {
	err := os.MkdirAll(directory, 0777)
	if err != nil {
		return err
	}

	// extract all common headers into mixin
	commonHeaders := ExtractCommonHeaders(events)
	test := &Test{
		Name: fmt.Sprintf("Common Header of %s", suitename),
		Request: ht.Request{
			Header: commonHeaders,
		},
	}

	commonFilename := path.Join(directory, "common-headers.mixin")
	err = writeTest(test, commonFilename)
	if err != nil {
		return err
	}

	suite := Suite{
		Name:        suitename,
		Description: fmt.Sprintf("Generated at %s", time.Now()),
		Variables: map[string]string{
			"HOSTNAME": remoteHost,
		},
	}

	for _, e := range events {
		host := e.Request.URL.Host
		e.Request.URL.Host = "H.O.S.T.N.A.M.E"
		cookies := []ht.Cookie{}
		for _, c := range e.Request.Cookies() {
			cookies = append(cookies, ht.Cookie{Name: c.Name, Value: c.Value})
		}
		e.Request.Header.Del("Cookie")

		params := e.Request.URL.Query()
		e.Request.URL.RawQuery = ""
		urlString := e.Request.URL.String()
		urlString = strings.Replace(urlString, "H.O.S.T.N.A.M.E", "{{HOSTNAME}}", 1)

		// TODO: scan body for parameters and set ParamsAs
		body := e.Body

		checks := extractChecks(e)

		test := &Test{
			Name:        e.Name,
			Description: fmt.Sprintf("Recorded from %s on %s", host, time.Now()),
			BasedOn:     []string{commonFilename},
			Request: ht.Request{
				Method:  e.Request.Method,
				URL:     urlString,
				Cookies: cookies,
				Header:  e.Request.Header,
				Params:  ht.URLValues(params),
				Body:    string(body),
			},
			Checks: checks,
		}

		name := strings.ToLower(strings.Replace(e.Name, " ", "_", -1)) + ".ht"
		suite.Tests = append(suite.Tests, name)
		filename := path.Join(directory, name)
		err = writeTest(test, filename)
		if err != nil {
			return err
		}

		e.Request.URL.Host = host
		log.Println("Generate test for ", e.Request.Method, e.Request.URL, " --> ", filename)
	}

	name := strings.ToLower(strings.Replace(suitename, " ", "_", -1)) + ".suite"
	filename := path.Join(directory, name)
	err = writeSuite(suite, filename)
	if err != nil {
		return err
	}
	log.Println("Generate suite ", filename)

	return nil
}

func writeTest(test *Test, filename string) error {
	data, err := json5.MarshalIndent(test, "", "    ")
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, data, 0666)
	if err != nil {
		return err
	}
	return nil
}

// TODO: combine with writeTest
func writeSuite(suite Suite, filename string) error {
	data, err := json5.MarshalIndent(suite, "", "    ")
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, data, 0666)
	if err != nil {
		return err
	}
	return nil
}

func extractChecks(e Event) ht.CheckList {
	list := ht.CheckList{}

	// Allways add StatusCode check.
	list = append(list, ht.StatusCode{Expect: e.Response.Code})

	// Check for Content-Type header.
	contentType := e.Response.Header().Get("Content-Type")
	if contentType != "" {
		contentType = strings.TrimSpace(strings.Split(contentType, ";")[0])
		if i := strings.Index(contentType, "/"); i != -1 {
			contentType := contentType[i+1:]
			list = append(list, ht.ContentType{Is: contentType})
		} else {
			contentType = ""
		}
	}

	// Checks for Set-Cookie headers:
	dummy := http.Response{Header: e.Response.Header()}
	now := e.Timestamp
	for _, c := range dummy.Cookies() {
		path := cookiePath(c, e.Request.URL)
		if c.MaxAge < 0 || (!c.Expires.IsZero() && c.Expires.Before(now)) {
			dc := &ht.DeleteCookie{Name: c.Name, Path: path}
			list = append(list, dc)
		} else {
			sc := createSetCookieCheck(c, now)
			sc.Path = ht.Condition{Equals: path}
			list = append(list, sc)
		}
	}

	// Check redirections:
	if loc := e.Response.HeaderMap.Get("Location"); loc != "" && e.Response.Code/100 == 3 { //  Uaaahhrg!
		red := &ht.Redirect{To: loc, StatusCode: e.Response.Code}
		list = append(list, red)
	}

	// Some HTML stuff
	if contentType == "html" || contentType == "xhtml" {

	}

	return list
}

func cookiePath(c *http.Cookie, u *url.URL) string {
	if c.Path != "" {
		return c.Path // assume this is well-formed
	}

	p := u.Path
	i := strings.LastIndex(p, "/")
	if i == 0 {
		return "/" // p ~ "/XYZ"
	}
	return p[:i] // Either p ~ "/XYZ/ABC" or p ~ "/XYZ/ABC/"
}

func createSetCookieCheck(c *http.Cookie, now time.Time) *ht.SetCookie {
	sc := &ht.SetCookie{Name: c.Name, Value: ht.Condition{Equals: c.Value}}

	lt := time.Duration(0)
	if c.MaxAge > 0 {
		lt = time.Second * time.Duration(c.MaxAge)
	} else if !c.Expires.IsZero() && c.Expires.After(now) {
		lt = c.Expires.Sub(now)
	}

	flags := []string{}
	if c.HttpOnly {
		flags = append(flags, "httpOnly")
	} else {
		flags = append(flags, "exposed")
	}
	if c.Secure {
		flags = append(flags, "secure")
	} else {
		flags = append(flags, "unsafe")
	}
	if lt > 0 {
		flags = append(flags, "persistent")
		if lt > 10*time.Second {
			lt -= 10 * time.Second
		}
		sc.MinLifetime = ht.Duration(lt)
	} else {
		flags = append(flags, "session")
	}
	sc.Type = strings.Join(flags, " ")

	return sc
}

// ExtractCommonHeaders from events.
func ExtractCommonHeaders(events []Event) http.Header {
	common := http.Header{}
	h0 := events[0].Request.Header
	for h, v := range h0 {
		vs := fmt.Sprintf("%v", v)
		identical := true
		for j := 2; j < len(events); j++ {
			if vs != fmt.Sprintf("%v", events[j].Request.Header[h]) {
				identical = false
				break
			}
		}
		if identical {
			common[h] = v
			for _, e := range events {
				e.Request.Header.Del(h)
			}
		}
	}
	return common
}
<commit_msg>recorder: add more autogenerated tests<commit_after>// Copyright 2015 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// recorder is allows to capture request/response pairs via a
// reverse proxy and generate tests for these pairs.
package recorder

import (
	"bytes"
	"crypto/sha1"
	"fmt"
	"image"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"net/url"
	"os"
	"path"
	"regexp"
	"strings"
	"time"

	"golang.org/x/net/html"

	"github.com/andybalholm/cascadia"
	"github.com/vdobler/ht/fingerprint"
	"github.com/vdobler/ht/ht"
	"github.com/vdobler/ht/internal/json5"
)

// Events is the global list of recorded events.
var Events []Event

// Event is a request/response pair
type Event struct {
	Request   *http.Request              // The request
	Response  *httptest.ResponseRecorder // The recorded response
	Body      []byte                     // The captured body
	Timestamp time.Time                  // Timestamp when caputred
	Name      string                     // Used during dumping
}

type Options struct {
	// Disarm is the time span after a captured request/response pair
	// in which the capturing is disarmed.
	Disarm time.Duration

	IgnoredContentType *regexp.Regexp
	IgnoredPath        *regexp.Regexp
}

func (o Options) ignore(e Event) bool {
	if o.IgnoredPath != nil && o.IgnoredPath.MatchString(e.Request.URL.Path) {
		log.Println("Ignoring path", e.Request.URL.Path)
		return true
	}
	if o.IgnoredContentType != nil &&
		o.IgnoredContentType.MatchString(e.Response.HeaderMap.Get("Content-Type")) {
		log.Println("Ignoring content type ", e.Response.HeaderMap.Get("Content-Type"))
		return true
	}
	return false
}

var (
	remoteHost string
)

// StartReverseProxy listens on the local port and forwards request to remote
// while capturing the request/response pairs.
func StartReverseProxy(port string, remote *url.URL, opts Options) error {
	remoteHost = remote.Host
	requests := make(chan Event, 10)
	go process(requests, opts)

	proxy := httputil.NewSingleHostReverseProxy(remote)
	http.HandleFunc("/", handler(proxy, requests))
	log.Printf("Staring reverse proxying from localhost:%s to %s", port, remote.String())
	return http.ListenAndServe(port, nil)
}

func handler(p *httputil.ReverseProxy, requests chan Event) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		rr := httptest.NewRecorder()
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			panic(err.Error()) // Harsh but what else?
		}
		r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
		p.ServeHTTP(rr, r)
		requests <- Event{
			Request:   r,
			Response:  rr,
			Body:      body,
			Timestamp: time.Now()}
		for h, v := range rr.HeaderMap {
			w.Header()[h] = v
		}
		w.WriteHeader(rr.Code)
		w.Write(rr.Body.Bytes())
	}
}

func process(requests chan Event, opts Options) {
	log.Println("Started processing")
	last := time.Now()
	for e := range requests {
		delta := e.Timestamp.Sub(last)
		if delta < opts.Disarm {
			continue
		}
		if opts.ignore(e) {
			continue
		}
		last = e.Timestamp
		e.Name = fmt.Sprintf("Event %d", len(Events)+1)
		Events = append(Events, e)
		log.Println("Recorded", e.Request.Method, e.Request.URL, " --> ", e.Response.Code)
	}
}

// Test is a reduced version of ht.Test suitable for serialization to JSON5.
type Test struct {
	Name        string
	Description string   `json:",omitempty"`
	BasedOn     []string `json:",omitempty"`
	Request     ht.Request
	Checks      ht.CheckList `json:",omitempty"`
}

// Suite is a reduced version of ht.Suite suitable to serialization to JSON5.
type Suite struct {
	Name        string
	Description string `json:",omitempty"`
	Tests       []string
	Variables   map[string]string
}

// DumpEvents writes events to directory, it extracts common headers.
func DumpEvents(events []Event, directory string, suitename string) error {
	err := os.MkdirAll(directory, 0777)
	if err != nil {
		return err
	}

	// extract all common headers into mixin
	commonHeaders := ExtractCommonHeaders(events)
	test := &Test{
		Name: fmt.Sprintf("Common Header of %s", suitename),
		Request: ht.Request{
			Header: commonHeaders,
		},
	}

	commonFilename := path.Join(directory, "common-headers.mixin")
	err = writeTest(test, commonFilename)
	if err != nil {
		return err
	}

	suite := Suite{
		Name:        suitename,
		Description: fmt.Sprintf("Generated at %s", time.Now()),
		Variables: map[string]string{
			"HOSTNAME": remoteHost,
		},
	}

	for _, e := range events {
		host := e.Request.URL.Host
		e.Request.URL.Host = "H.O.S.T.N.A.M.E"
		cookies := []ht.Cookie{}
		for _, c := range e.Request.Cookies() {
			cookies = append(cookies, ht.Cookie{Name: c.Name, Value: c.Value})
		}
		e.Request.Header.Del("Cookie")

		params := e.Request.URL.Query()
		e.Request.URL.RawQuery = ""
		urlString := e.Request.URL.String()
		urlString = strings.Replace(urlString, "H.O.S.T.N.A.M.E", "{{HOSTNAME}}", 1)

		// TODO: scan body for parameters and set ParamsAs
		body := e.Body

		checks := extractChecks(e)

		test := &Test{
			Name:        e.Name,
			Description: fmt.Sprintf("Recorded from %s on %s", host, time.Now()),
			BasedOn:     []string{commonFilename},
			Request: ht.Request{
				Method:  e.Request.Method,
				URL:     urlString,
				Cookies: cookies,
				Header:  e.Request.Header,
				Params:  ht.URLValues(params),
				Body:    string(body),
			},
			Checks: checks,
		}

		name := strings.ToLower(strings.Replace(e.Name, " ", "_", -1)) + ".ht"
		suite.Tests = append(suite.Tests, name)
		filename := path.Join(directory, name)
		err = writeTest(test, filename)
		if err != nil {
			return err
		}

		e.Request.URL.Host = host
		log.Println("Generate test for ", e.Request.Method, e.Request.URL, " --> ", filename)
	}

	name := strings.ToLower(strings.Replace(suitename, " ", "_", -1)) + ".suite"
	filename := path.Join(directory, name)
	err = writeSuite(suite, filename)
	if err != nil {
		return err
	}
	log.Println("Generate suite ", filename)

	return nil
}

func writeTest(test *Test, filename string) error {
	data, err := json5.MarshalIndent(test, "", "    ")
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, data, 0666)
	if err != nil {
		return err
	}
	return nil
}

// TODO: combine with writeTest
func writeSuite(suite Suite, filename string) error {
	data, err := json5.MarshalIndent(suite, "", "    ")
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, data, 0666)
	if err != nil {
		return err
	}
	return nil
}

// extractChecks tries to generate checks based on the given
// request/response pair in e.
func extractChecks(e Event) ht.CheckList {
	list := ht.CheckList{}

	// Allways add StatusCode check.
	list = append(list, ht.StatusCode{Expect: e.Response.Code})

	// Check for Content-Type header.
	contentType := e.Response.Header().Get("Content-Type")
	contentTypeParts := []string{"??", "??"}
	if contentType != "" {
		contentType = strings.TrimSpace(strings.Split(contentType, ";")[0])
		if i := strings.Index(contentType, "/"); i != -1 {
			contentTypeParts = strings.SplitN(contentType, "/", 2)
			list = append(list, ht.ContentType{Is: contentTypeParts[1]})
		}
	}

	// Checks for Set-Cookie headers:
	dummy := http.Response{Header: e.Response.Header()}
	now := e.Timestamp
	for _, c := range dummy.Cookies() {
		path := cookiePath(c, e.Request.URL)
		if c.MaxAge < 0 || (!c.Expires.IsZero() && c.Expires.Before(now)) {
			dc := &ht.DeleteCookie{Name: c.Name, Path: path}
			list = append(list, dc)
		} else {
			sc := createSetCookieCheck(c, now)
			sc.Path = ht.Condition{Equals: path}
			list = append(list, sc)
		}
	}

	// Check redirections:
	if loc := e.Response.HeaderMap.Get("Location"); loc != "" && e.Response.Code/100 == 3 { //  Uaaahhrg!
		red := &ht.Redirect{To: loc, StatusCode: e.Response.Code}
		list = append(list, red)
	}

	// Based on content type
	switch {
	case contentTypeParts[1] == "html", contentTypeParts[1] == "xhtml":
		list = append(list, extractHTMLChecks(e)...)
	case contentTypeParts[0] == "image":
		list = append(list, extractImageChecks(e)...)
	case contentTypeParts[1] == "pdf":
		list = append(list, identityCheck(e))
	}

	return list
}

var (
	htmlTitleSel = cascadia.MustCompile("head title")
	htmlH1Sel    = cascadia.MustCompile("body h1")
)

func identityCheck(e Event) ht.Check {
	hash := sha1.Sum(e.Body)
	return ht.Identity{SHA1: fmt.Sprintf("%02x", hash)}
}

func extractHTMLChecks(e Event) ht.CheckList {
	list := ht.CheckList{}

	// Allways add Links check.
	list = append(list, &ht.Links{
		Head:        true,
		Which:       "a img link script",
		Concurrency: 4,
		Timeout:     ht.Duration(20 * time.Second),
		IgnoredLinks: []ht.Condition{
			ht.Condition{Contains: "www.facebook.com/"},
			ht.Condition{Contains: "www.twitter.com/"},
		},
	})

	doc, err := html.Parse(bytes.NewBuffer(e.Body))
	if err != nil {
		return list
	}

	// Title
	if node := htmlTitleSel.MatchFirst(doc); node != nil {
		title := ht.TextContent(node, false)
		list = append(list, &ht.HTMLContains{
			Selector: "head title",
			Text:     []string{title},
			Complete: true,
		})
	}

	// All h1
	if nodes := htmlH1Sel.MatchAll(doc); len(nodes) != 0 {
		h1s := []string{}
		for _, node := range nodes {
			h1s = append(h1s, ht.TextContent(node, false))
		}
		list = append(list, &ht.HTMLContains{
			Selector: "head title",
			Text:     h1s,
			Complete: true,
			InOrder:  true,
		})
	}

	return list
}

func extractImageChecks(e Event) ht.CheckList {
	list := ht.CheckList{}

	image, format, err := image.Decode(bytes.NewBuffer(e.Body))
	if err != nil {
		return list
	}

	list = append(list, ht.Image{
		Format: format,
		Width:  image.Bounds().Dx(),
		Height: image.Bounds().Dy(),
	})

	BMV := fingerprint.NewBMVHash(image)
	list = append(list, ht.Image{
		Fingerprint: BMV.String(),
		Threshold:   0.01,
	})

	ch := fingerprint.NewColorHist(image)
	list = append(list, ht.Image{
		Fingerprint: ch.String(),
		Threshold:   0.01,
	})

	return list
}

func cookiePath(c *http.Cookie, u *url.URL) string {
	if c.Path != "" {
		return c.Path // assume this is well-formed
	}

	p := u.Path
	i := strings.LastIndex(p, "/")
	if i == 0 {
		return "/" // p ~ "/XYZ"
	}
	return p[:i] // Either p ~ "/XYZ/ABC" or p ~ "/XYZ/ABC/"
}

func createSetCookieCheck(c *http.Cookie, now time.Time) *ht.SetCookie {
	sc := &ht.SetCookie{Name: c.Name, Value: ht.Condition{Equals: c.Value}}

	lt := time.Duration(0)
	if c.MaxAge > 0 {
		lt = time.Second * time.Duration(c.MaxAge)
	} else if !c.Expires.IsZero() && c.Expires.After(now) {
		lt = c.Expires.Sub(now)
	}

	flags := []string{}
	if c.HttpOnly {
		flags = append(flags, "httpOnly")
	} else {
		flags = append(flags, "exposed")
	}
	if c.Secure {
		flags = append(flags, "secure")
	} else {
		flags = append(flags, "unsafe")
	}
	if lt > 0 {
		flags = append(flags, "persistent")
		if lt > 10*time.Second {
			lt -= 10 * time.Second
		}
		sc.MinLifetime = ht.Duration(lt)
	} else {
		flags = append(flags, "session")
	}
	sc.Type = strings.Join(flags, " ")

	return sc
}

// ExtractCommonHeaders from events.
func ExtractCommonHeaders(events []Event) http.Header {
	common := http.Header{}
	h0 := events[0].Request.Header
	for h, v := range h0 {
		vs := fmt.Sprintf("%v", v)
		identical := true
		for j := 2; j < len(events); j++ {
			if vs != fmt.Sprintf("%v", events[j].Request.Header[h]) {
				identical = false
				break
			}
		}
		if identical {
			common[h] = v
			for _, e := range events {
				e.Request.Header.Del(h)
			}
		}
	}
	return common
}
<|endoftext|>


<commit_before>package gcfg

import (
	"fmt"
	"log"
)

func ExampleParseString() {
	cfgStr := `; Comment line
[section]
name=value # comment`
	cfg := struct {
		Section struct {
			Name string
		}
	}{}
	err := ParseString(&cfg, cfgStr)
	if err != nil {
		log.Fatalf("Failed to parse INI data: %s", err)
	}
	fmt.Println(cfg.Section.Name)
	// Output: value
}
<commit_msg>add example with subsections<commit_after>package gcfg

import (
	"fmt"
	"log"
)

func ExampleParseString() {
	cfgStr := `; Comment line
[section]
name=value # comment`
	cfg := struct {
		Section struct {
			Name string
		}
	}{}
	err := ParseString(&cfg, cfgStr)
	if err != nil {
		log.Fatalf("Failed to parse INI data: %s", err)
	}
	fmt.Println(cfg.Section.Name)
	// Output: value
}

func ExampleParseString_subsections() {
	cfgStr := `; Comment line
[profile "A"]
color = white

[profile "B"]
color = black
`
	cfg := struct {
		Profile map[string]*struct {
			Color string
		}
	}{}
	err := ParseString(&cfg, cfgStr)
	if err != nil {
		log.Fatalf("Failed to parse INI data: %s", err)
	}
	fmt.Printf("%s %s\n", cfg.Profile["A"].Color, cfg.Profile["B"].Color)
	// Output: white black
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gcs

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"unicode/utf8"

	"github.com/jacobsa/gcloud/httputil"
	"golang.org/x/net/context"
	"google.golang.org/api/googleapi"
	storagev1 "google.golang.org/api/storage/v1"
)

// Create the JSON for an "object resource", for use in an Objects.insert body.
func serializeMetadata(
	bucketName string,
	req *CreateObjectRequest) (out []byte, err error) {
	// Convert to storagev1.Object.
	rawObject, err := toRawObject(bucketName, req)
	if err != nil {
		err = fmt.Errorf("toRawObject: %v", err)
		return
	}

	// Serialize.
	out, err = json.Marshal(rawObject)
	if err != nil {
		err = fmt.Errorf("json.Marshal: %v", err)
		return
	}

	return
}

func startResumableUpload(
	httpClient *http.Client,
	bucketName string,
	ctx context.Context,
	req *CreateObjectRequest) (uploadURL *url.URL, err error) {
	// Construct an appropriate URL.
	//
	// The documentation (http://goo.gl/IJSlVK) is extremely vague about how this
	// is supposed to work. As of 2015-03-26, it simply gives an example:
	//
	//     POST https://www.googleapis.com/upload/storage/v1/b/<bucket>/o
	//
	// In Google-internal bug 19718068, it was clarified that the intent is that
	// the bucket name be encoded into a single path segment, as defined by RFC
	// 3986.
	bucketSegment := httputil.EncodePathSegment(bucketName)
	opaque := fmt.Sprintf(
		"//www.googleapis.com/upload/storage/v1/b/%s/o",
		bucketSegment)

	url := &url.URL{
		Scheme:   "https",
		Opaque:   opaque,
		RawQuery: "uploadType=resumable&projection=full",
	}

	if req.GenerationPrecondition != nil {
		url.RawQuery = fmt.Sprintf(
			"%s&ifGenerationMatch=%v",
			url.RawQuery,
			*req.GenerationPrecondition)
	}

	// Serialize the object metadata to JSON, for the request body.
	metadataJson, err := serializeMetadata(bucketName, req)
	if err != nil {
		err = fmt.Errorf("serializeMetadata: %v", err)
		return
	}

	// Create the HTTP request.
	httpReq, err := httputil.NewRequest(
		"POST",
		url,
		bytes.NewReader(metadataJson),
		userAgent)

	if err != nil {
		err = fmt.Errorf("httputil.NewRequest: %v", err)
		return
	}

	// Set up HTTP request headers.
	httpReq.Header.Set("Content-Type", "application/json")
	httpReq.Header.Set("X-Upload-Content-Type", req.ContentType)

	// Execute the HTTP request.
	httpRes, err := httpClient.Do(httpReq)
	if err != nil {
		return
	}

	defer googleapi.CloseBody(httpRes)

	// Check for HTTP-level errors.
	if err = googleapi.CheckResponse(httpRes); err != nil {
		return
	}

	// Extract the Location header.
	str := httpRes.Header.Get("Location")
	if str == "" {
		err = fmt.Errorf("Expected a Location header.")
		return
	}

	// Parse it.
	uploadURL, err = url.Parse(str)
	if err != nil {
		err = fmt.Errorf("url.Parse: %v", err)
		return
	}

	return
}

func createObject(
	httpClient *http.Client,
	bucketName string,
	ctx context.Context,
	req *CreateObjectRequest) (o *Object, err error) {
	// We encode using json.NewEncoder, which is documented to silently transform
	// invalid UTF-8 (cf. http://goo.gl/3gIUQB). So we can't rely on the server
	// to detect this for us.
	if !utf8.ValidString(req.Name) {
		err = errors.New("Invalid object name: not valid UTF-8")
		return
	}

	// Start a resumable upload, obtaining an upload URL.
	uploadURL, err := startResumableUpload(
		httpClient,
		bucketName,
		ctx,
		req)

	if err != nil {
		return
	}

	// Set up a follow-up request to the upload URL.
	httpReq, err := httputil.NewRequest("PUT", uploadURL, req.Contents, userAgent)
	if err != nil {
		err = fmt.Errorf("httputil.NewRequest: %v", err)
		return
	}

	httpReq.Header.Set("Content-Type", req.ContentType)

	// Execute the request.
	httpRes, err := httpClient.Do(httpReq)
	if err != nil {
		return
	}

	defer googleapi.CloseBody(httpRes)

	// Check for HTTP-level errors.
	if err = googleapi.CheckResponse(httpRes); err != nil {
		// Special case: handle precondition errors.
		if typed, ok := err.(*googleapi.Error); ok {
			if typed.Code == http.StatusPreconditionFailed {
				err = &PreconditionError{Err: typed}
			}
		}

		return
	}

	// Parse the response.
	var rawObject *storagev1.Object
	if err = json.NewDecoder(httpRes.Body).Decode(&rawObject); err != nil {
		return
	}

	// Convert the response.
	if o, err = toObject(rawObject); err != nil {
		err = fmt.Errorf("toObject: %v", err)
		return
	}

	return
}
<commit_msg>Cleaned up a helper function.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gcs

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"unicode/utf8"

	"github.com/jacobsa/gcloud/httputil"
	"golang.org/x/net/context"
	"google.golang.org/api/googleapi"
	storagev1 "google.golang.org/api/storage/v1"
)

// Create the JSON for an "object resource", for use as an Objects.insert body.
func makeCreateObjectBody(
	bucketName string,
	req *CreateObjectRequest) (r io.Reader, err error) {
	// Convert to storagev1.Object.
	rawObject, err := toRawObject(bucketName, req)
	if err != nil {
		err = fmt.Errorf("toRawObject: %v", err)
		return
	}

	// Serialize.
	b, err := json.Marshal(rawObject)
	if err != nil {
		err = fmt.Errorf("json.Marshal: %v", err)
		return
	}

	// Create a Reader.
	r = bytes.NewReader(b)

	return
}

func startResumableUpload(
	httpClient *http.Client,
	bucketName string,
	ctx context.Context,
	req *CreateObjectRequest) (uploadURL *url.URL, err error) {
	// Construct an appropriate URL.
	//
	// The documentation (http://goo.gl/IJSlVK) is extremely vague about how this
	// is supposed to work. As of 2015-03-26, it simply gives an example:
	//
	//     POST https://www.googleapis.com/upload/storage/v1/b/<bucket>/o
	//
	// In Google-internal bug 19718068, it was clarified that the intent is that
	// the bucket name be encoded into a single path segment, as defined by RFC
	// 3986.
	bucketSegment := httputil.EncodePathSegment(bucketName)
	opaque := fmt.Sprintf(
		"//www.googleapis.com/upload/storage/v1/b/%s/o",
		bucketSegment)

	url := &url.URL{
		Scheme:   "https",
		Opaque:   opaque,
		RawQuery: "uploadType=resumable&projection=full",
	}

	if req.GenerationPrecondition != nil {
		url.RawQuery = fmt.Sprintf(
			"%s&ifGenerationMatch=%v",
			url.RawQuery,
			*req.GenerationPrecondition)
	}

	// Set up the request body.
	body, err := makeCreateObjectBody(bucketName, req)
	if err != nil {
		err = fmt.Errorf("makeCreateObjectBody: %v", err)
		return
	}

	// Create the HTTP request.
	httpReq, err := httputil.NewRequest(
		"POST",
		url,
		body,
		userAgent)

	if err != nil {
		err = fmt.Errorf("httputil.NewRequest: %v", err)
		return
	}

	// Set up HTTP request headers.
	httpReq.Header.Set("Content-Type", "application/json")
	httpReq.Header.Set("X-Upload-Content-Type", req.ContentType)

	// Execute the HTTP request.
	httpRes, err := httpClient.Do(httpReq)
	if err != nil {
		return
	}

	defer googleapi.CloseBody(httpRes)

	// Check for HTTP-level errors.
	if err = googleapi.CheckResponse(httpRes); err != nil {
		return
	}

	// Extract the Location header.
	str := httpRes.Header.Get("Location")
	if str == "" {
		err = fmt.Errorf("Expected a Location header.")
		return
	}

	// Parse it.
	uploadURL, err = url.Parse(str)
	if err != nil {
		err = fmt.Errorf("url.Parse: %v", err)
		return
	}

	return
}

func createObject(
	httpClient *http.Client,
	bucketName string,
	ctx context.Context,
	req *CreateObjectRequest) (o *Object, err error) {
	// We encode using json.NewEncoder, which is documented to silently transform
	// invalid UTF-8 (cf. http://goo.gl/3gIUQB). So we can't rely on the server
	// to detect this for us.
	if !utf8.ValidString(req.Name) {
		err = errors.New("Invalid object name: not valid UTF-8")
		return
	}

	// Start a resumable upload, obtaining an upload URL.
	uploadURL, err := startResumableUpload(
		httpClient,
		bucketName,
		ctx,
		req)

	if err != nil {
		return
	}

	// Set up a follow-up request to the upload URL.
	httpReq, err := httputil.NewRequest("PUT", uploadURL, req.Contents, userAgent)
	if err != nil {
		err = fmt.Errorf("httputil.NewRequest: %v", err)
		return
	}

	httpReq.Header.Set("Content-Type", req.ContentType)

	// Execute the request.
	httpRes, err := httpClient.Do(httpReq)
	if err != nil {
		return
	}

	defer googleapi.CloseBody(httpRes)

	// Check for HTTP-level errors.
	if err = googleapi.CheckResponse(httpRes); err != nil {
		// Special case: handle precondition errors.
		if typed, ok := err.(*googleapi.Error); ok {
			if typed.Code == http.StatusPreconditionFailed {
				err = &PreconditionError{Err: typed}
			}
		}

		return
	}

	// Parse the response.
	var rawObject *storagev1.Object
	if err = json.NewDecoder(httpRes.Body).Decode(&rawObject); err != nil {
		return
	}

	// Convert the response.
	if o, err = toObject(rawObject); err != nil {
		err = fmt.Errorf("toObject: %v", err)
		return
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"go/format"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
	"text/template"
)

// ResourceGenerator takes AWS CloudFormation Resource Specification
// documents, and generates Go structs and a JSON Schema from them.
type ResourceGenerator struct {
	primaryUrl   string
	fragmentUrls map[string]string
	Results      *ResourceGeneratorResults
}

// ResourceGeneratorResults contains a summary of the items generated
type ResourceGeneratorResults struct {
	UpdatedResources map[string]string
	UpdatedSchemas   map[string]string
	ProcessedCount   int
}

// NewResourceGenerator contains a primary AWS CloudFormation Resource Specification
// document and an array of fragment Resource Specification documents (such as transforms),
// and generates Go structs and a JSON Schema from them.
// The input can be a mix of URLs (https://) or files (file://).
func NewResourceGenerator(primaryUrl string, fragmentUrls map[string]string) (*ResourceGenerator, error) {

	rg := &ResourceGenerator{
		primaryUrl:   primaryUrl,
		fragmentUrls: fragmentUrls,
		Results: &ResourceGeneratorResults{
			UpdatedResources: map[string]string{},
			UpdatedSchemas:   map[string]string{},
			ProcessedCount:   0,
		},
	}

	return rg, nil
}

// Generate generates Go structs and a JSON Schema from the AWS CloudFormation
// Resource Specifications provided to NewResourceGenerator
func (rg *ResourceGenerator) Generate() error {

	// Process the primary template first, since the primary template resources
	// are added to the JSON schema for fragment transform specs
	fmt.Printf("Downloading cloudformation specification from %s\n", rg.primaryUrl)
	primaryData, err := rg.downloadSpec(rg.primaryUrl)
	if err != nil {
		return err
	}
	primarySpec, err := rg.processSpec("cloudformation", primaryData)
	if err != nil {
		return err
	}
	if err := rg.generateJSONSchema("cloudformation", primarySpec); err != nil {
		return err
	}

	for name, url := range rg.fragmentUrls {
		fmt.Printf("Downloading %s specification from %s\n", name, url)
		data, err := rg.downloadSpec(url)
		if err != nil {
			return err
		}
		spec, err := rg.processSpec(name, data)
		if err != nil {
			return err
		}
		// Append main CloudFormation schema resource types and property types to this fragment
		for k, v := range primarySpec.Resources {
			spec.Resources[k] = v
		}
		for k, v := range primarySpec.Properties {
			spec.Properties[k] = v
		}
		if err := rg.generateJSONSchema(name, spec); err != nil {
			return err
		}
	}

	return nil
}

func (rg *ResourceGenerator) downloadSpec(location string) ([]byte, error) {
	uri, err := url.Parse(location)
	if err != nil {
		return nil, err
	}

	switch uri.Scheme {
	case "https", "http":
		uri := uri.String()
		response, err := http.Get(uri)
		if err != nil {
			return nil, err
		}

		data, err := ioutil.ReadAll(response.Body)
		if err != nil {
			return nil, err
		}

		return data, nil
	case "file":
		data, err := ioutil.ReadFile(strings.Replace(location, "file://", "", -1))
		if err != nil {
			return nil, err
		}

		return data, nil
	}

	return nil, fmt.Errorf("invalid URL scheme %s", uri.Scheme)
}

func (rg *ResourceGenerator) processSpec(specname string, data []byte) (*CloudFormationResourceSpecification, error) {

	// Unmarshall the JSON specification
	spec := &CloudFormationResourceSpecification{}
	if err := json.Unmarshal(data, spec); err != nil {
		return nil, err
	}

	// Write all of the resources in the spec file
	for name, resource := range spec.Resources {
		if err := rg.generateResources(name, resource, false, spec); err != nil {
			return nil, err
		}
	}

	// Write all of the custom types in the spec file
	for name, resource := range spec.Properties {
		if err := rg.generateResources(name, resource, true, spec); err != nil {
			return nil, err
		}
	}

	return spec, nil

}

func (rg *ResourceGenerator) generateResources(name string, resource Resource, isCustomProperty bool, spec *CloudFormationResourceSpecification) error {

	// Open the resource template
	tmpl, err := template.ParseFiles("generate/templates/resource.template")
	if err != nil {
		return fmt.Errorf("failed to load resource template: %s", err)
	}

	// Pass in the following information into the template
	sname := structName(name)
	structNameParts := strings.Split(name, ".")
	basename := structName(structNameParts[0])

	templateData := struct {
		Name             string
		StructName       string
		Basename         string
		Resource         Resource
		IsCustomProperty bool
		Version          string
	}{
		Name:             name,
		StructName:       sname,
		Basename:         basename,
		Resource:         resource,
		IsCustomProperty: isCustomProperty,
		Version:          spec.ResourceSpecificationVersion,
	}

	// Execute the template, writing it to a buffer
	var buf bytes.Buffer
	err = tmpl.Execute(&buf, templateData)
	if err != nil {
		return fmt.Errorf("failed to generate resource %s: %s", name, err)
	}

	// Format the generated Go code with gofmt
	formatted, err := format.Source(buf.Bytes())
	if err != nil {
		return fmt.Errorf("failed to format Go file for resource %s: %s", name, err)
	}

	// Check if the file has changed since the last time generate ran
	fn := "cloudformation/" + filename(name)
	current, err := ioutil.ReadFile(fn)

	if err != nil || bytes.Compare(formatted, current) != 0 {

		// Write the file contents out
		if err := ioutil.WriteFile(fn, formatted, 0644); err != nil {
			return fmt.Errorf("failed to write resource file %s: %s", fn, err)
		}

		// Log the updated resource name to the results
		rg.Results.UpdatedResources[fn] = name

	}

	rg.Results.ProcessedCount++

	return nil

}

func (rg *ResourceGenerator) generateJSONSchema(specname string, spec *CloudFormationResourceSpecification) error {

	// Open the schema template and setup a counter function that will
	// available in the template to be used to detect when trailing commas
	// are required in the JSON when looping through maps
	tmpl, err := template.New("schema.template").Funcs(template.FuncMap{
		"counter": counter,
	}).ParseFiles("generate/templates/schema.template")

	var buf bytes.Buffer

	// Execute the template, writing it to file
	err = tmpl.Execute(&buf, spec)
	if err != nil {
		return fmt.Errorf("failed to generate JSON Schema: %s", err)
	}

	// Parse it to JSON objects and back again to format it
	var j interface{}
	if err := json.Unmarshal(buf.Bytes(), &j); err != nil {
		return fmt.Errorf("failed to unmarshal JSON Schema: %s", err)
	}

	formatted, err := json.MarshalIndent(j, "", "    ")
	if err != nil {
		return fmt.Errorf("failed to marshal JSON Schema: %s", err)

	}

	filename := fmt.Sprintf("schema/%s.schema.json", specname)
	if err := ioutil.WriteFile(filename, formatted, 0644); err != nil {
		return fmt.Errorf("failed to write JSON Schema: %s", err)
	}

	rg.Results.UpdatedSchemas[filename] = specname

	return nil

}

func generatePolymorphicProperty(name string, property Property) {

	log.Printf("LOG: Generating %s\n", name)

	// Open the polymorphic property template
	tmpl, err := template.New("polymorphic-property.template").Funcs(template.FuncMap{
		"convertToGoType": convertTypeToGo,
	}).ParseFiles("generate/templates/polymorphic-property.template")

	nameParts := strings.Split(name, "_")

	types := append([]string{}, property.PrimitiveTypes...)
	types = append(types, property.PrimitiveItemTypes...)
	types = append(types, property.ItemTypes...)
	types = append(types, property.Types...)

	templateData := struct {
		Name        string
		Basename    string
		Property    Property
		Types       []string
		TypesJoined string
	}{
		Name:        name,
		Basename:    nameParts[0],
		Property:    property,
		Types:       types,
		TypesJoined: conjoin("or", types),
	}

	// Execute the template, writing it to file
	var buf bytes.Buffer
	err = tmpl.Execute(&buf, templateData)
	if err != nil {
		fmt.Printf("Error: Failed to generate polymorphic property %s\n%s\n", name, err)
		os.Exit(1)
	}

	// Format the generated Go file with gofmt
	formatted, err := format.Source(buf.Bytes())
	if err != nil {
		fmt.Printf("Error: Failed to format Go file for resource %s\n%s\n", name, err)
		os.Exit(1)
	}

	// Write the file out
	if err := ioutil.WriteFile("cloudformation/"+filename(name), formatted, 0644); err != nil {
		fmt.Printf("Error: Failed to write JSON Schema\n%s\n", err)
		os.Exit(1)
	}

}

// counter is used within the JSON Schema template to determin whether or not
// to put a comma after a JSON resource (i.e. if it's the last element, then no comma)
// see: http://android.wekeepcoding.com/article/10126058/Go+template+remove+the+last+comma+in+range+loop
func counter(length int) func() int {
	i := length
	return func() int {
		i--
		return i
	}
}

func conjoin(conj string, items []string) string {
	if len(items) == 0 {
		return ""
	}
	if len(items) == 1 {
		return items[0]
	}
	if len(items) == 2 { // "a and b" not "a, and b"
		return items[0] + " " + conj + " " + items[1]
	}

	sep := ", "
	pieces := []string{items[0]}
	for _, item := range items[1 : len(items)-1] {
		pieces = append(pieces, sep, item)
	}
	pieces = append(pieces, sep, conj, " ", items[len(items)-1])

	return strings.Join(pieces, "")
}
<commit_msg>Removed debug logging<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"go/format"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strings"
	"text/template"
)

// ResourceGenerator takes AWS CloudFormation Resource Specification
// documents, and generates Go structs and a JSON Schema from them.
type ResourceGenerator struct {
	primaryUrl   string
	fragmentUrls map[string]string
	Results      *ResourceGeneratorResults
}

// ResourceGeneratorResults contains a summary of the items generated
type ResourceGeneratorResults struct {
	UpdatedResources map[string]string
	UpdatedSchemas   map[string]string
	ProcessedCount   int
}

// NewResourceGenerator contains a primary AWS CloudFormation Resource Specification
// document and an array of fragment Resource Specification documents (such as transforms),
// and generates Go structs and a JSON Schema from them.
// The input can be a mix of URLs (https://) or files (file://).
func NewResourceGenerator(primaryUrl string, fragmentUrls map[string]string) (*ResourceGenerator, error) {

	rg := &ResourceGenerator{
		primaryUrl:   primaryUrl,
		fragmentUrls: fragmentUrls,
		Results: &ResourceGeneratorResults{
			UpdatedResources: map[string]string{},
			UpdatedSchemas:   map[string]string{},
			ProcessedCount:   0,
		},
	}

	return rg, nil
}

// Generate generates Go structs and a JSON Schema from the AWS CloudFormation
// Resource Specifications provided to NewResourceGenerator
func (rg *ResourceGenerator) Generate() error {

	// Process the primary template first, since the primary template resources
	// are added to the JSON schema for fragment transform specs
	fmt.Printf("Downloading cloudformation specification from %s\n", rg.primaryUrl)
	primaryData, err := rg.downloadSpec(rg.primaryUrl)
	if err != nil {
		return err
	}
	primarySpec, err := rg.processSpec("cloudformation", primaryData)
	if err != nil {
		return err
	}
	if err := rg.generateJSONSchema("cloudformation", primarySpec); err != nil {
		return err
	}

	for name, url := range rg.fragmentUrls {
		fmt.Printf("Downloading %s specification from %s\n", name, url)
		data, err := rg.downloadSpec(url)
		if err != nil {
			return err
		}
		spec, err := rg.processSpec(name, data)
		if err != nil {
			return err
		}
		// Append main CloudFormation schema resource types and property types to this fragment
		for k, v := range primarySpec.Resources {
			spec.Resources[k] = v
		}
		for k, v := range primarySpec.Properties {
			spec.Properties[k] = v
		}
		if err := rg.generateJSONSchema(name, spec); err != nil {
			return err
		}
	}

	return nil
}

func (rg *ResourceGenerator) downloadSpec(location string) ([]byte, error) {
	uri, err := url.Parse(location)
	if err != nil {
		return nil, err
	}

	switch uri.Scheme {
	case "https", "http":
		uri := uri.String()
		response, err := http.Get(uri)
		if err != nil {
			return nil, err
		}

		data, err := ioutil.ReadAll(response.Body)
		if err != nil {
			return nil, err
		}

		return data, nil
	case "file":
		data, err := ioutil.ReadFile(strings.Replace(location, "file://", "", -1))
		if err != nil {
			return nil, err
		}

		return data, nil
	}

	return nil, fmt.Errorf("invalid URL scheme %s", uri.Scheme)
}

func (rg *ResourceGenerator) processSpec(specname string, data []byte) (*CloudFormationResourceSpecification, error) {

	// Unmarshall the JSON specification
	spec := &CloudFormationResourceSpecification{}
	if err := json.Unmarshal(data, spec); err != nil {
		return nil, err
	}

	// Write all of the resources in the spec file
	for name, resource := range spec.Resources {
		if err := rg.generateResources(name, resource, false, spec); err != nil {
			return nil, err
		}
	}

	// Write all of the custom types in the spec file
	for name, resource := range spec.Properties {
		if err := rg.generateResources(name, resource, true, spec); err != nil {
			return nil, err
		}
	}

	return spec, nil

}

func (rg *ResourceGenerator) generateResources(name string, resource Resource, isCustomProperty bool, spec *CloudFormationResourceSpecification) error {

	// Open the resource template
	tmpl, err := template.ParseFiles("generate/templates/resource.template")
	if err != nil {
		return fmt.Errorf("failed to load resource template: %s", err)
	}

	// Pass in the following information into the template
	sname := structName(name)
	structNameParts := strings.Split(name, ".")
	basename := structName(structNameParts[0])

	templateData := struct {
		Name             string
		StructName       string
		Basename         string
		Resource         Resource
		IsCustomProperty bool
		Version          string
	}{
		Name:             name,
		StructName:       sname,
		Basename:         basename,
		Resource:         resource,
		IsCustomProperty: isCustomProperty,
		Version:          spec.ResourceSpecificationVersion,
	}

	// Execute the template, writing it to a buffer
	var buf bytes.Buffer
	err = tmpl.Execute(&buf, templateData)
	if err != nil {
		return fmt.Errorf("failed to generate resource %s: %s", name, err)
	}

	// Format the generated Go code with gofmt
	formatted, err := format.Source(buf.Bytes())
	if err != nil {
		return fmt.Errorf("failed to format Go file for resource %s: %s", name, err)
	}

	// Check if the file has changed since the last time generate ran
	fn := "cloudformation/" + filename(name)
	current, err := ioutil.ReadFile(fn)

	if err != nil || bytes.Compare(formatted, current) != 0 {

		// Write the file contents out
		if err := ioutil.WriteFile(fn, formatted, 0644); err != nil {
			return fmt.Errorf("failed to write resource file %s: %s", fn, err)
		}

		// Log the updated resource name to the results
		rg.Results.UpdatedResources[fn] = name

	}

	rg.Results.ProcessedCount++

	return nil

}

func (rg *ResourceGenerator) generateJSONSchema(specname string, spec *CloudFormationResourceSpecification) error {

	// Open the schema template and setup a counter function that will
	// available in the template to be used to detect when trailing commas
	// are required in the JSON when looping through maps
	tmpl, err := template.New("schema.template").Funcs(template.FuncMap{
		"counter": counter,
	}).ParseFiles("generate/templates/schema.template")

	var buf bytes.Buffer

	// Execute the template, writing it to file
	err = tmpl.Execute(&buf, spec)
	if err != nil {
		return fmt.Errorf("failed to generate JSON Schema: %s", err)
	}

	// Parse it to JSON objects and back again to format it
	var j interface{}
	if err := json.Unmarshal(buf.Bytes(), &j); err != nil {
		return fmt.Errorf("failed to unmarshal JSON Schema: %s", err)
	}

	formatted, err := json.MarshalIndent(j, "", "    ")
	if err != nil {
		return fmt.Errorf("failed to marshal JSON Schema: %s", err)

	}

	filename := fmt.Sprintf("schema/%s.schema.json", specname)
	if err := ioutil.WriteFile(filename, formatted, 0644); err != nil {
		return fmt.Errorf("failed to write JSON Schema: %s", err)
	}

	rg.Results.UpdatedSchemas[filename] = specname

	return nil

}

func generatePolymorphicProperty(name string, property Property) {

	// Open the polymorphic property template
	tmpl, err := template.New("polymorphic-property.template").Funcs(template.FuncMap{
		"convertToGoType": convertTypeToGo,
	}).ParseFiles("generate/templates/polymorphic-property.template")

	nameParts := strings.Split(name, "_")

	types := append([]string{}, property.PrimitiveTypes...)
	types = append(types, property.PrimitiveItemTypes...)
	types = append(types, property.ItemTypes...)
	types = append(types, property.Types...)

	templateData := struct {
		Name        string
		Basename    string
		Property    Property
		Types       []string
		TypesJoined string
	}{
		Name:        name,
		Basename:    nameParts[0],
		Property:    property,
		Types:       types,
		TypesJoined: conjoin("or", types),
	}

	// Execute the template, writing it to file
	var buf bytes.Buffer
	err = tmpl.Execute(&buf, templateData)
	if err != nil {
		fmt.Printf("Error: Failed to generate polymorphic property %s\n%s\n", name, err)
		os.Exit(1)
	}

	// Format the generated Go file with gofmt
	formatted, err := format.Source(buf.Bytes())
	if err != nil {
		fmt.Printf("Error: Failed to format Go file for resource %s\n%s\n", name, err)
		os.Exit(1)
	}

	// Write the file out
	if err := ioutil.WriteFile("cloudformation/"+filename(name), formatted, 0644); err != nil {
		fmt.Printf("Error: Failed to write JSON Schema\n%s\n", err)
		os.Exit(1)
	}

}

// counter is used within the JSON Schema template to determin whether or not
// to put a comma after a JSON resource (i.e. if it's the last element, then no comma)
// see: http://android.wekeepcoding.com/article/10126058/Go+template+remove+the+last+comma+in+range+loop
func counter(length int) func() int {
	i := length
	return func() int {
		i--
		return i
	}
}

func conjoin(conj string, items []string) string {
	if len(items) == 0 {
		return ""
	}
	if len(items) == 1 {
		return items[0]
	}
	if len(items) == 2 { // "a and b" not "a, and b"
		return items[0] + " " + conj + " " + items[1]
	}

	sep := ", "
	pieces := []string{items[0]}
	for _, item := range items[1 : len(items)-1] {
		pieces = append(pieces, sep, item)
	}
	pieces = append(pieces, sep, conj, " ", items[len(items)-1])

	return strings.Join(pieces, "")
}
<|endoftext|>


<commit_before>/*
Copyright 2017 WALLIX

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
	"bytes"
	"fmt"
)

var (
	Version  = "v0.1.8"
	BuildFor string

	buildSha, buildDate, buildArch, buildOS string
)

type BuildInfo struct {
	Version, Sha, Date, Arch, OS, For string
}

func (b BuildInfo) String() string {
	var buff bytes.Buffer
	buff.WriteString(fmt.Sprintf("version=%s", b.Version))

	if b.Sha != "" {
		buff.WriteString(fmt.Sprintf(", commit=%s", b.Sha))
	}
	if b.Date != "" {
		buff.WriteString(fmt.Sprintf(", build-date=%s", b.Date))
	}
	if b.Arch != "" {
		buff.WriteString(fmt.Sprintf(", build-arch=%s", b.Arch))
	}
	if b.OS != "" {
		buff.WriteString(fmt.Sprintf(", build-os=%s", b.OS))
	}
	if b.For != "" {
		buff.WriteString(fmt.Sprintf(", build-for=%s", b.For))
	}
	return buff.String()
}

var CurrentBuildInfo = BuildInfo{
	Version: Version,
	For:     BuildFor,
	Sha:     buildSha,
	Date:    buildDate,
	Arch:    buildArch,
	OS:      buildOS,
}
<commit_msg>Bumping to current dev version<commit_after>/*
Copyright 2017 WALLIX

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
	"bytes"
	"fmt"
)

var (
	Version  = "v0.1.9"
	BuildFor string

	buildSha, buildDate, buildArch, buildOS string
)

type BuildInfo struct {
	Version, Sha, Date, Arch, OS, For string
}

func (b BuildInfo) String() string {
	var buff bytes.Buffer
	buff.WriteString(fmt.Sprintf("version=%s", b.Version))

	if b.Sha != "" {
		buff.WriteString(fmt.Sprintf(", commit=%s", b.Sha))
	}
	if b.Date != "" {
		buff.WriteString(fmt.Sprintf(", build-date=%s", b.Date))
	}
	if b.Arch != "" {
		buff.WriteString(fmt.Sprintf(", build-arch=%s", b.Arch))
	}
	if b.OS != "" {
		buff.WriteString(fmt.Sprintf(", build-os=%s", b.OS))
	}
	if b.For != "" {
		buff.WriteString(fmt.Sprintf(", build-for=%s", b.For))
	}
	return buff.String()
}

var CurrentBuildInfo = BuildInfo{
	Version: Version,
	For:     BuildFor,
	Sha:     buildSha,
	Date:    buildDate,
	Arch:    buildArch,
	OS:      buildOS,
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package block

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/ciao-project/ciao/ciao-controller/types"
	"github.com/gorilla/mux"
)

// VersionStatus defines whether a reported version is supported or not.
type VersionStatus string

const (
	// Deprecated indicates the api deprecates the spec version.
	Deprecated VersionStatus = "DEPRECATED"

	// Supported indicates a spec version is supported by the api
	Supported VersionStatus = "SUPPORTED"

	// Current indicates the current spec version of the api
	// TBD: can this be eliminated? do we need both supported & current?
	Current VersionStatus = "CURRENT"
)

// Link is used by the API to create the link json strings.
type Link struct {
	Href string `json:"href"`
	Type string `json:"type,omitempty"`
	Rel  string `json:"rel,omitempty"`
}

// MediaType indicates whether the api supports json or xml
type MediaType struct {
	Base string `json:"base"`
	Type string `json:"type"`
}

// Version contains information about the api version that is supported.
type Version struct {
	Status     VersionStatus `json:"status"`
	Updated    string        `json:"updated"`
	Links      []Link        `json:"links"`
	MinVersion string        `json:"min_version"`
	Version    string        `json:"version"`
	MediaTypes []MediaType   `json:"media-types"`
	ID         string        `json:"id"`
}

// Versions is used to create the version api strings.
type Versions struct {
	Versions []Version `json:"versions"`
}

// Choice is used to indicate the api choices that are supported.
type Choice struct {
	Status     VersionStatus `json:"status"`
	MediaTypes []MediaType   `json:"media-types"`
	ID         string        `json:"id"`
	Links      []Link        `json:"links"`
}

// Choices is used to create the choices json strings.
type Choices struct {
	Choices []Choice `json:"choices"`
}

// AbsoluteLimits implements the block api absolute limits object.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#showAbsoluteLimits
// Note that an absolute limit value of -1 indicates that the limit is infinite.
type AbsoluteLimits struct {
	TotalSnapshotsUsed       int `json:"totalSnapshotsUsed"`
	MaxTotalBackups          int `json:"maxTotalBackups"`
	MaxTotalVolumeGigabytes  int `json:"maxTotalVolumeGigabytes"`
	MaxTotalSnapshots        int `json:"maxTotalSnapshots"`
	MaxTotalBackupGigabytes  int `json:"maxTotalBackupGigabytes"`
	TotalBackupGigabytesUsed int `json:"totalBackupGigabytesUsed"`
	MaxTotalVolumes          int `json:"maxTotalVolumes"`
	TotalVolumesUsed         int `json:"totalVolumesUsed"`
	TotalBackupsUsed         int `json:"totalBackupsUsed"`
	TotalGigabytesUsed       int `json:"totalGigabytesUsed"`
}

// Limit implements the limit object.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#showAbsoluteLimits
type Limit struct {
	Rate     []string       `json:"rate"`
	Absolute AbsoluteLimits `json:"absolute"`
}

// Limits implements the limits object.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#showAbsoluteLimits
type Limits struct {
	Limits Limit `json:"limits"`
}

// VolumeStatus is the status of create, list, update, or delete volumes.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#volumes-v2-volumes
type VolumeStatus string

const (
	// Creating indicates that a volume is being created.
	Creating VolumeStatus = "creating"

	// Available indicates that a volume is ready to be used.
	Available VolumeStatus = "available"

	// Attaching indicates that a volume is being attached.
	Attaching VolumeStatus = "attaching"

	// InUse indicates that a volume is attached to an instance.
	InUse VolumeStatus = "in-use"

	// Deleting indicates that a volume is being deleted.
	Deleting VolumeStatus = "deleting"

	// Error indicates that a volume creation error occurred.
	Error VolumeStatus = "error"

	// ErrorDeleting indicates that a volume deletion error occurred.
	ErrorDeleting VolumeStatus = "error_deleting"

	// BackingUp indicates that the volume is being backed up.
	BackingUp VolumeStatus = "backing-up"

	// RestoringBackup indicates that a backup is being restored
	// to the volume.
	RestoringBackup VolumeStatus = "restoring-backup"

	// ErrorRestoring indicates that a backup restoration error occurred.
	ErrorRestoring VolumeStatus = "error_restoring"

	// ErrorExtending indicates that an error occurred
	// while attempting to extend a volume.
	ErrorExtending VolumeStatus = "error_extending"
)

// MetaData is defined as a set of arbitrary key value structs.
type MetaData interface{}

// RequestedVolume contains information about a volume to be created.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#createVolume
type RequestedVolume struct {
	Size               int      `json:"size"`
	AvailabilityZone   string   `json:"availability_zone,omitempty"`
	SourceVolID        string   `json:"source_volid,omitempty"`
	Description        string   `json:"description,omitempty"`
	MultiAttach        bool     `json:"multiattach"`
	SnapshotID         string   `json:"snapshot_id,omitempty"`
	Name               string   `json:"name,omitempty"`
	ImageRef           string   `json:"imageRef,omitempty"`
	VolumeType         string   `json:"volume_type,omitempty"`
	MetaData           MetaData `json:"metadata"`
	SourceReplica      string   `json:"source_replica,omitempty"`
	ConsistencyGroupID string   `json:"consistencygroup_id,omitempty"`
}

// VolumeCreateRequest is the json request for the createVolume endpoint.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#createVolume
type VolumeCreateRequest struct {
	Volume RequestedVolume `json:"volume"`
}

// Attachment contains instance attachment information.
// If this volume is attached to a server, the attachment contains
// information about the attachment.
type Attachment struct {
	ServerUUID     string `json:"server_id"`
	AttachmentUUID string `json:"attachment_id"`
	HostName       string `json:"host_name"`
	VolumeUUID     string `json:"volume_id"`
	Device         string `json:"device"`
	DeviceUUID     string `json:"id"`
}

const (
	// ReplicationDisabled indicates that replication is not enabled.
	ReplicationDisabled string = "disabled"
)

// Volume contains information about a volume that has been created or updated.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#createVolume
// http://developer.openstack.org/api-ref-blockstorage-v2.html#updateVolume
type Volume struct {
	Status             VolumeStatus `json:"status"`
	MigrationStatus    string       `json:"migration_status,omitempty"`
	UserID             string       `json:"user_id,omitempty"`
	Attachments        []Attachment `json:"attachments"`
	Links              []Link       `json:"links"`
	AvailabilityZone   string       `json:"availability_zone,omitempty"`
	Bootable           string       `json:"bootable,omitempty"`
	Encrypted          bool         `json:"encrypted"`
	CreatedAt          *time.Time   `json:"created_at"`
	Description        string       `json:"description,omitempty"`
	UpdatedAt          *time.Time   `json:"updated_at"`
	VolumeType         string       `json:"volume_type,omitempty"`
	Name               string       `json:"name,omitempty"`
	ReplicationStatus  string       `json:"replication_status,omitempty"`
	ConsistencyGroupID string       `json:"consistencygroup_id,omitempty"`
	SourceVolID        string       `json:"source_volid,omitempty"`
	SnapshotID         string       `json:"snapshot_id,omitempty"`
	MultiAttach        bool         `json:"multiattach"`
	MetaData           MetaData     `json:"metadata"`
	ID                 string       `json:"id,omitempty"`
	Size               int          `json:"size"`
}

// VolumeResponse is the json response for the createVolume, and updateVolume endpoint.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#createVolume
// http://developer.openstack.org/api-ref-blockstorage-v2.html#updateVolume
type VolumeResponse struct {
	Volume Volume `json:"volume"`
}

// VolumeDetail contains volume information for the listVolumeDetails endpoint.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#listVolumesDetail
type VolumeDetail struct {
	MigrationStatus          string       `json:"migration_status,omitempty"`
	Attachments              []Attachment `json:"attachments,omitempty"`
	Links                    []Link       `json:"links,omitempty"`
	AvailabilityZone         string       `json:"availability_zone,omitempty"`
	OSVolHostAttr            string       `json:"os-vol-host-attr:host"`
	Encrypted                bool         `json:"encrypted"`
	UpdatedAt                *time.Time   `json:"updated_at,omitempty"`
	OSVolReplicationExStatus string       `json:"os-volume-replication:extended_status,omitempty"`
	ReplicationStatus        string       `json:"replication_status"`
	SnapshotID               string       `json:"snapshot_id,omitempty"`
	ID                       string       `json:"id"`
	Size                     int          `json:"size"`
	UserID                   string       `json:"user_id"`
	OSVolTenantAttr          string       `json:"os-vol-tenant-attr:tenant_id"`
	OSVolMigStatusAttrStatus string       `json:"os-vol-mig-status-attr:migstat,omitempty"`
	MetaData                 MetaData     `json:"metadata,omitempty"`
	Status                   VolumeStatus `json:"status"`
	Description              string       `json:"description,omitempty"`
	MultiAttach              bool         `json:"multiattach"`
	OSVolReplicationDriver   string       `json:"os-volume-replication:driver_data,omitempty"`
	SourceVolID              string       `json:"source_volid,omitempty"`
	ConsistencyGroupID       string       `json:"consistencygroup_id,omitempty"`
	OSVolMigStatusAttrNameID string       `json:"os-vol-mig-status-attr:name_id,omitempty"`
	Name                     string       `json:"name"`
	Bootable                 string       `json:"bootable"`
	CreatedAt                *time.Time   `json:"created_at"`
	VolumeType               string       `json:"volume_type,omitempty"`
}

// ListVolumesDetail is the json response for the listVolumeDetails endpoint.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#listVolumesDetail
type ListVolumesDetail struct {
	Volumes []VolumeDetail `json:"volumes"`
}

// ShowVolumeDetails is the json response for the showVolumeDetail endpoint.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#showVolume
type ShowVolumeDetails struct {
	Volume VolumeDetail `json:"volume"`
}

// These errors can be returned by the Service interface
var (
	ErrQuota                = errors.New("Tenant over quota")
	ErrTenantNotFound       = errors.New("Tenant not found")
	ErrVolumeNotFound       = errors.New("Volume not found")
	ErrInstanceNotFound     = errors.New("Instance not found")
	ErrVolumeNotAvailable   = errors.New("Volume not available")
	ErrVolumeOwner          = errors.New("You are not volume owner")
	ErrInstanceOwner        = errors.New("You are not instance owner")
	ErrInstanceNotAvailable = errors.New("Instance not available")
	ErrVolumeNotAttached    = errors.New("Volume not attached")
)

// errorResponse maps service error responses to http responses.
// this helper function can help functions avoid having to switch
// on return values all the time.
func errorResponse(err error) APIResponse {
	switch err {
	case ErrQuota:
		return APIResponse{http.StatusForbidden, nil}
	case ErrTenantNotFound:
		return APIResponse{http.StatusNotFound, nil}
	case ErrVolumeNotFound:
		return APIResponse{http.StatusNotFound, nil}
	case ErrInstanceNotFound:
		return APIResponse{http.StatusNotFound, nil}
	case ErrVolumeNotAvailable,
		ErrVolumeNotAvailable,
		ErrVolumeOwner,
		ErrInstanceOwner,
		ErrInstanceNotAvailable,
		ErrVolumeNotAttached:
		return APIResponse{http.StatusForbidden, nil}
	default:
		return APIResponse{http.StatusInternalServerError, nil}
	}
}

// APIConfig contains information needed to start the block api service.
type APIConfig struct {
	VolService Service // the service interface
}

// Service contains the required interface to the block service.
// The caller who is starting the api service needs to provide this
// interface.
type Service interface {
	CreateVolume(tenant string, req RequestedVolume) (types.BlockData, error)
	DeleteVolume(tenant string, volume string) error
	AttachVolume(tenant string, volume string, instance string, mountpoint string) error
	DetachVolume(tenant string, volume string, attachment string) error
	ListVolumesDetail(tenant string) ([]types.BlockData, error)
	ShowVolumeDetails(tenant string, volume string) (types.BlockData, error)
}

// Context contains data and interfaces that the block api will need.
// TBD: do we really need this, or is just a service interface sufficient?
type Context struct {
	Service
}

// APIResponse is returned from the API handlers.
type APIResponse struct {
	status   int
	response interface{}
}

// APIHandler is a custom handler for the block APIs.
// This custom handler allows us to more cleanly return an error and response,
// and pass some package level context into the handler.
type APIHandler struct {
	*Context
	Handler func(*Context, http.ResponseWriter, *http.Request) (APIResponse, error)
}

// ServeHTTP satisfies the http Handler interface.
// It wraps our api response in json as well.
func (h APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resp, err := h.Handler(h.Context, w, r)
	if err != nil {
		http.Error(w, err.Error(), resp.status)
	}

	b, err := json.Marshal(resp.response)
	if err != nil {
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(resp.status)
	w.Write(b)
}

func createVolume(bc *Context, w http.ResponseWriter, r *http.Request) (APIResponse, error) {
	vars := mux.Vars(r)
	tenant := vars["tenant"]

	defer r.Body.Close()

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return APIResponse{http.StatusBadRequest, nil}, err
	}

	var req VolumeCreateRequest
	err = json.Unmarshal(body, &req)
	if err != nil {
		return APIResponse{http.StatusInternalServerError, nil}, err
	}

	vol, err := bc.CreateVolume(tenant, req.Volume)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusAccepted, vol}, nil
}

func listVolumesDetail(bc *Context, w http.ResponseWriter, r *http.Request) (APIResponse, error) {
	vars := mux.Vars(r)
	tenant := vars["tenant"]

	// TBD: support sorting and paging

	vols, err := bc.ListVolumesDetail(tenant)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusOK, vols}, nil
}

func showVolumeDetails(bc *Context, w http.ResponseWriter, r *http.Request) (APIResponse, error) {
	vars := mux.Vars(r)
	tenant := vars["tenant"]
	volume := vars["volume_id"]

	vol, err := bc.ShowVolumeDetails(tenant, volume)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusOK, vol}, nil
}

func deleteVolume(bc *Context, w http.ResponseWriter, r *http.Request) (APIResponse, error) {
	vars := mux.Vars(r)
	tenant := vars["tenant"]
	volume := vars["volume_id"]

	// TBD - satisfy preconditions here, or in interface?
	err := bc.DeleteVolume(tenant, volume)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusAccepted, nil}, nil
}

func volumeActionAttach(bc *Context, m map[string]interface{}, tenant string, volume string) (APIResponse, error) {
	val := m["os-attach"]

	m = val.(map[string]interface{})

	val, ok := m["instance_uuid"]
	if !ok {
		// we have to have the instance uuid
		return APIResponse{http.StatusBadRequest, nil}, nil
	}
	instance := val.(string)

	val, ok = m["mountpoint"]
	if !ok {
		// we have to have the mountpoint ?
		return APIResponse{http.StatusBadRequest, nil}, nil
	}
	mountPoint := val.(string)

	err := bc.AttachVolume(tenant, volume, instance, mountPoint)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusAccepted, nil}, nil
}

func volumeActionDetach(bc *Context, m map[string]interface{}, tenant string, volume string) (APIResponse, error) {
	val := m["os-detach"]

	m = val.(map[string]interface{})

	// attachment-id is optional
	var attachment string
	val = m["attachment-id"]
	if val != nil {
		attachment = val.(string)
	}

	err := bc.DetachVolume(tenant, volume, attachment)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusAccepted, nil}, nil
}

func volumeAction(bc *Context, w http.ResponseWriter, r *http.Request) (APIResponse, error) {
	vars := mux.Vars(r)
	tenant := vars["tenant"]
	volume := vars["volume_id"]

	var req interface{}

	defer r.Body.Close()

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return APIResponse{http.StatusBadRequest, nil}, err
	}

	err = json.Unmarshal(body, &req)
	if err != nil {
		return APIResponse{http.StatusInternalServerError, nil}, err
	}

	m := req.(map[string]interface{})

	// for now, we will support only attach and detach

	if m["os-attach"] != nil {
		return volumeActionAttach(bc, m, tenant, volume)
	}

	if m["os-detach"] != nil {
		return volumeActionDetach(bc, m, tenant, volume)
	}

	return APIResponse{http.StatusBadRequest, nil}, err
}

// Routes provides gorilla mux routes for the supported endpoints.
func Routes(config APIConfig, r *mux.Router) *mux.Router {
	// make new Context
	context := &Context{config.VolService}

	if r == nil {
		r = mux.NewRouter()
	}

	// Volumes
	r.Handle("/v2/{tenant}/volumes",
		APIHandler{context, createVolume}).Methods("POST")
	r.Handle("/v2/{tenant}/volumes",
		APIHandler{context, listVolumesDetail}).Methods("GET")
	r.Handle("/v2/{tenant}/volumes/{volume_id}",
		APIHandler{context, showVolumeDetails}).Methods("GET")
	r.Handle("/v2/{tenant}/volumes/{volume_id}",
		APIHandler{context, deleteVolume}).Methods("DELETE")

	// Volume actions
	r.Handle("/v2/{tenant}/volumes/{volume_id}/action",
		APIHandler{context, volumeAction}).Methods("POST")

	return r
}
<commit_msg>openstack/block: Remove unused structures and defines<commit_after>// Copyright (c) 2016 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package block

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"

	"github.com/ciao-project/ciao/ciao-controller/types"
	"github.com/gorilla/mux"
)

// MetaData is defined as a set of arbitrary key value structs.
type MetaData interface{}

// RequestedVolume contains information about a volume to be created.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#createVolume
type RequestedVolume struct {
	Size               int      `json:"size"`
	AvailabilityZone   string   `json:"availability_zone,omitempty"`
	SourceVolID        string   `json:"source_volid,omitempty"`
	Description        string   `json:"description,omitempty"`
	MultiAttach        bool     `json:"multiattach"`
	SnapshotID         string   `json:"snapshot_id,omitempty"`
	Name               string   `json:"name,omitempty"`
	ImageRef           string   `json:"imageRef,omitempty"`
	VolumeType         string   `json:"volume_type,omitempty"`
	MetaData           MetaData `json:"metadata"`
	SourceReplica      string   `json:"source_replica,omitempty"`
	ConsistencyGroupID string   `json:"consistencygroup_id,omitempty"`
}

// VolumeCreateRequest is the json request for the createVolume endpoint.
// http://developer.openstack.org/api-ref-blockstorage-v2.html#createVolume
type VolumeCreateRequest struct {
	Volume RequestedVolume `json:"volume"`
}

// These errors can be returned by the Service interface
var (
	ErrQuota                = errors.New("Tenant over quota")
	ErrTenantNotFound       = errors.New("Tenant not found")
	ErrVolumeNotFound       = errors.New("Volume not found")
	ErrInstanceNotFound     = errors.New("Instance not found")
	ErrVolumeNotAvailable   = errors.New("Volume not available")
	ErrVolumeOwner          = errors.New("You are not volume owner")
	ErrInstanceOwner        = errors.New("You are not instance owner")
	ErrInstanceNotAvailable = errors.New("Instance not available")
	ErrVolumeNotAttached    = errors.New("Volume not attached")
)

// errorResponse maps service error responses to http responses.
// this helper function can help functions avoid having to switch
// on return values all the time.
func errorResponse(err error) APIResponse {
	switch err {
	case ErrQuota:
		return APIResponse{http.StatusForbidden, nil}
	case ErrTenantNotFound:
		return APIResponse{http.StatusNotFound, nil}
	case ErrVolumeNotFound:
		return APIResponse{http.StatusNotFound, nil}
	case ErrInstanceNotFound:
		return APIResponse{http.StatusNotFound, nil}
	case ErrVolumeNotAvailable,
		ErrVolumeNotAvailable,
		ErrVolumeOwner,
		ErrInstanceOwner,
		ErrInstanceNotAvailable,
		ErrVolumeNotAttached:
		return APIResponse{http.StatusForbidden, nil}
	default:
		return APIResponse{http.StatusInternalServerError, nil}
	}
}

// APIConfig contains information needed to start the block api service.
type APIConfig struct {
	VolService Service // the service interface
}

// Service contains the required interface to the block service.
// The caller who is starting the api service needs to provide this
// interface.
type Service interface {
	CreateVolume(tenant string, req RequestedVolume) (types.BlockData, error)
	DeleteVolume(tenant string, volume string) error
	AttachVolume(tenant string, volume string, instance string, mountpoint string) error
	DetachVolume(tenant string, volume string, attachment string) error
	ListVolumesDetail(tenant string) ([]types.BlockData, error)
	ShowVolumeDetails(tenant string, volume string) (types.BlockData, error)
}

// Context contains data and interfaces that the block api will need.
// TBD: do we really need this, or is just a service interface sufficient?
type Context struct {
	Service
}

// APIResponse is returned from the API handlers.
type APIResponse struct {
	status   int
	response interface{}
}

// APIHandler is a custom handler for the block APIs.
// This custom handler allows us to more cleanly return an error and response,
// and pass some package level context into the handler.
type APIHandler struct {
	*Context
	Handler func(*Context, http.ResponseWriter, *http.Request) (APIResponse, error)
}

// ServeHTTP satisfies the http Handler interface.
// It wraps our api response in json as well.
func (h APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resp, err := h.Handler(h.Context, w, r)
	if err != nil {
		http.Error(w, err.Error(), resp.status)
	}

	b, err := json.Marshal(resp.response)
	if err != nil {
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(resp.status)
	w.Write(b)
}

func createVolume(bc *Context, w http.ResponseWriter, r *http.Request) (APIResponse, error) {
	vars := mux.Vars(r)
	tenant := vars["tenant"]

	defer r.Body.Close()

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return APIResponse{http.StatusBadRequest, nil}, err
	}

	var req VolumeCreateRequest
	err = json.Unmarshal(body, &req)
	if err != nil {
		return APIResponse{http.StatusInternalServerError, nil}, err
	}

	vol, err := bc.CreateVolume(tenant, req.Volume)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusAccepted, vol}, nil
}

func listVolumesDetail(bc *Context, w http.ResponseWriter, r *http.Request) (APIResponse, error) {
	vars := mux.Vars(r)
	tenant := vars["tenant"]

	// TBD: support sorting and paging

	vols, err := bc.ListVolumesDetail(tenant)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusOK, vols}, nil
}

func showVolumeDetails(bc *Context, w http.ResponseWriter, r *http.Request) (APIResponse, error) {
	vars := mux.Vars(r)
	tenant := vars["tenant"]
	volume := vars["volume_id"]

	vol, err := bc.ShowVolumeDetails(tenant, volume)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusOK, vol}, nil
}

func deleteVolume(bc *Context, w http.ResponseWriter, r *http.Request) (APIResponse, error) {
	vars := mux.Vars(r)
	tenant := vars["tenant"]
	volume := vars["volume_id"]

	// TBD - satisfy preconditions here, or in interface?
	err := bc.DeleteVolume(tenant, volume)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusAccepted, nil}, nil
}

func volumeActionAttach(bc *Context, m map[string]interface{}, tenant string, volume string) (APIResponse, error) {
	val := m["os-attach"]

	m = val.(map[string]interface{})

	val, ok := m["instance_uuid"]
	if !ok {
		// we have to have the instance uuid
		return APIResponse{http.StatusBadRequest, nil}, nil
	}
	instance := val.(string)

	val, ok = m["mountpoint"]
	if !ok {
		// we have to have the mountpoint ?
		return APIResponse{http.StatusBadRequest, nil}, nil
	}
	mountPoint := val.(string)

	err := bc.AttachVolume(tenant, volume, instance, mountPoint)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusAccepted, nil}, nil
}

func volumeActionDetach(bc *Context, m map[string]interface{}, tenant string, volume string) (APIResponse, error) {
	val := m["os-detach"]

	m = val.(map[string]interface{})

	// attachment-id is optional
	var attachment string
	val = m["attachment-id"]
	if val != nil {
		attachment = val.(string)
	}

	err := bc.DetachVolume(tenant, volume, attachment)
	if err != nil {
		return errorResponse(err), err
	}

	return APIResponse{http.StatusAccepted, nil}, nil
}

func volumeAction(bc *Context, w http.ResponseWriter, r *http.Request) (APIResponse, error) {
	vars := mux.Vars(r)
	tenant := vars["tenant"]
	volume := vars["volume_id"]

	var req interface{}

	defer r.Body.Close()

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return APIResponse{http.StatusBadRequest, nil}, err
	}

	err = json.Unmarshal(body, &req)
	if err != nil {
		return APIResponse{http.StatusInternalServerError, nil}, err
	}

	m := req.(map[string]interface{})

	// for now, we will support only attach and detach

	if m["os-attach"] != nil {
		return volumeActionAttach(bc, m, tenant, volume)
	}

	if m["os-detach"] != nil {
		return volumeActionDetach(bc, m, tenant, volume)
	}

	return APIResponse{http.StatusBadRequest, nil}, err
}

// Routes provides gorilla mux routes for the supported endpoints.
func Routes(config APIConfig, r *mux.Router) *mux.Router {
	// make new Context
	context := &Context{config.VolService}

	if r == nil {
		r = mux.NewRouter()
	}

	// Volumes
	r.Handle("/v2/{tenant}/volumes",
		APIHandler{context, createVolume}).Methods("POST")
	r.Handle("/v2/{tenant}/volumes",
		APIHandler{context, listVolumesDetail}).Methods("GET")
	r.Handle("/v2/{tenant}/volumes/{volume_id}",
		APIHandler{context, showVolumeDetails}).Methods("GET")
	r.Handle("/v2/{tenant}/volumes/{volume_id}",
		APIHandler{context, deleteVolume}).Methods("DELETE")

	// Volume actions
	r.Handle("/v2/{tenant}/volumes/{volume_id}/action",
		APIHandler{context, volumeAction}).Methods("POST")

	return r
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package terraform

import (
	"fmt"
	"sort"

	"github.com/hashicorp/hcl/v2/hclwrite"
	"github.com/zclconf/go-cty/cty"
	"github.com/zclconf/go-cty/cty/gocty"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/featureflag"
	"k8s.io/kops/upup/pkg/fi"
)

func (t *TerraformTarget) finishHCL2(taskMap map[string]fi.Task) error {
	f := hclwrite.NewEmptyFile()
	rootBody := f.Body()

	writeLocalsOutputs(rootBody, t.outputs)

	providerName := string(t.Cloud.ProviderID())
	if t.Cloud.ProviderID() == kops.CloudProviderGCE {
		providerName = "google"
	}
	providerBlock := rootBody.AppendNewBlock("provider", []string{providerName})
	providerBody := providerBlock.Body()
	providerBody.SetAttributeValue("region", cty.StringVal(t.Cloud.Region()))
	for k, v := range tfGetProviderExtraConfig(t.clusterSpecTarget) {
		providerBody.SetAttributeValue(k, cty.StringVal(v))
	}
	rootBody.AppendNewline()

	resourcesByType, err := t.getResourcesByType()
	if err != nil {
		return err
	}

	resourceTypes := make([]string, 0, len(resourcesByType))
	for resourceType := range resourcesByType {
		resourceTypes = append(resourceTypes, resourceType)
	}
	sort.Strings(resourceTypes)
	for _, resourceType := range resourceTypes {
		resources := resourcesByType[resourceType]
		resourceNames := make([]string, 0, len(resources))
		for resourceName := range resources {
			resourceNames = append(resourceNames, resourceName)
		}
		sort.Strings(resourceNames)
		for _, resourceName := range resourceNames {
			item := resources[resourceName]

			resBlock := rootBody.AppendNewBlock("resource", []string{resourceType, resourceName})
			resBody := resBlock.Body()
			resType, err := gocty.ImpliedType(item)
			if err != nil {
				return err
			}
			resVal, err := gocty.ToCtyValue(item, resType)
			if err != nil {
				return err
			}
			if resVal.IsNull() {
				continue
			}
			resVal.ForEachElement(func(key cty.Value, value cty.Value) bool {
				writeValue(resBody, key.AsString(), value)
				return false
			})
			rootBody.AppendNewline()
		}
	}

	terraformBlock := rootBody.AppendNewBlock("terraform", []string{})
	terraformBody := terraformBlock.Body()
	terraformBody.SetAttributeValue("required_version", cty.StringVal(">= 0.12.26"))

	requiredProvidersBlock := terraformBody.AppendNewBlock("required_providers", []string{})
	requiredProvidersBody := requiredProvidersBlock.Body()

	if t.Cloud.ProviderID() == kops.CloudProviderGCE {
		writeMap(requiredProvidersBody, "google", map[string]cty.Value{
			"source":  cty.StringVal("hashicorp/google"),
			"version": cty.StringVal(">= 2.19.0"),
		})
	} else if t.Cloud.ProviderID() == kops.CloudProviderAWS {
		writeMap(requiredProvidersBody, "aws", map[string]cty.Value{
			"source":  cty.StringVal("hashicorp/aws"),
			"version": cty.StringVal(">= 3.34.0"),
		})
		if featureflag.Spotinst.Enabled() {
			writeMap(requiredProvidersBody, "spotinst", map[string]cty.Value{
				"source":  cty.StringVal("spotinst/spotinst"),
				"version": cty.StringVal(">= 1.33.0"),
			})
		}
	}

	bytes := hclwrite.Format(f.Bytes())
	t.files["kubernetes.tf"] = bytes

	return nil
}

// writeLocalsOutputs creates the locals block and output blocks for all output variables
// Example:
// locals {
//   key1 = "value1"
//   key2 = "value2"
// }
// output "key1" {
//   value = "value1"
// }
// output "key2" {
//   value = "value2"
// }
func writeLocalsOutputs(body *hclwrite.Body, outputs map[string]*terraformOutputVariable) error {
	if len(outputs) == 0 {
		return nil
	}

	localsBlock := body.AppendNewBlock("locals", []string{})
	body.AppendNewline()
	// each output is added to a single locals block and its own output block
	localsBody := localsBlock.Body()
	existingOutputVars := make(map[string]bool)

	outputNames := make([]string, 0, len(outputs))
	for k := range outputs {
		outputNames = append(outputNames, k)
	}
	sort.Strings(outputNames)

	for _, n := range outputNames {
		v := outputs[n]
		tfName := tfSanitize(v.Key)
		outputBlock := body.AppendNewBlock("output", []string{tfName})
		outputBody := outputBlock.Body()
		if v.Value != nil {
			writeLiteral(outputBody, "value", v.Value)
			writeLiteral(localsBody, tfName, v.Value)
		} else {
			deduped, err := DedupLiterals(v.ValueArray)
			if err != nil {
				return err
			}
			writeLiteralList(outputBody, "value", deduped)
			writeLiteralList(localsBody, tfName, deduped)
		}

		if existingOutputVars[tfName] {
			return fmt.Errorf("duplicate variable found: %s", tfName)
		}
		existingOutputVars[tfName] = true
		body.AppendNewline()
	}
	return nil
}
<commit_msg>Adjust sorting of outputs in hcl2<commit_after>/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package terraform

import (
	"fmt"
	"sort"

	"github.com/hashicorp/hcl/v2/hclwrite"
	"github.com/zclconf/go-cty/cty"
	"github.com/zclconf/go-cty/cty/gocty"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/featureflag"
	"k8s.io/kops/upup/pkg/fi"
)

func (t *TerraformTarget) finishHCL2(taskMap map[string]fi.Task) error {
	f := hclwrite.NewEmptyFile()
	rootBody := f.Body()

	writeLocalsOutputs(rootBody, t.outputs)

	providerName := string(t.Cloud.ProviderID())
	if t.Cloud.ProviderID() == kops.CloudProviderGCE {
		providerName = "google"
	}
	providerBlock := rootBody.AppendNewBlock("provider", []string{providerName})
	providerBody := providerBlock.Body()
	providerBody.SetAttributeValue("region", cty.StringVal(t.Cloud.Region()))
	for k, v := range tfGetProviderExtraConfig(t.clusterSpecTarget) {
		providerBody.SetAttributeValue(k, cty.StringVal(v))
	}
	rootBody.AppendNewline()

	resourcesByType, err := t.getResourcesByType()
	if err != nil {
		return err
	}

	resourceTypes := make([]string, 0, len(resourcesByType))
	for resourceType := range resourcesByType {
		resourceTypes = append(resourceTypes, resourceType)
	}
	sort.Strings(resourceTypes)
	for _, resourceType := range resourceTypes {
		resources := resourcesByType[resourceType]
		resourceNames := make([]string, 0, len(resources))
		for resourceName := range resources {
			resourceNames = append(resourceNames, resourceName)
		}
		sort.Strings(resourceNames)
		for _, resourceName := range resourceNames {
			item := resources[resourceName]

			resBlock := rootBody.AppendNewBlock("resource", []string{resourceType, resourceName})
			resBody := resBlock.Body()
			resType, err := gocty.ImpliedType(item)
			if err != nil {
				return err
			}
			resVal, err := gocty.ToCtyValue(item, resType)
			if err != nil {
				return err
			}
			if resVal.IsNull() {
				continue
			}
			resVal.ForEachElement(func(key cty.Value, value cty.Value) bool {
				writeValue(resBody, key.AsString(), value)
				return false
			})
			rootBody.AppendNewline()
		}
	}

	terraformBlock := rootBody.AppendNewBlock("terraform", []string{})
	terraformBody := terraformBlock.Body()
	terraformBody.SetAttributeValue("required_version", cty.StringVal(">= 0.12.26"))

	requiredProvidersBlock := terraformBody.AppendNewBlock("required_providers", []string{})
	requiredProvidersBody := requiredProvidersBlock.Body()

	if t.Cloud.ProviderID() == kops.CloudProviderGCE {
		writeMap(requiredProvidersBody, "google", map[string]cty.Value{
			"source":  cty.StringVal("hashicorp/google"),
			"version": cty.StringVal(">= 2.19.0"),
		})
	} else if t.Cloud.ProviderID() == kops.CloudProviderAWS {
		writeMap(requiredProvidersBody, "aws", map[string]cty.Value{
			"source":  cty.StringVal("hashicorp/aws"),
			"version": cty.StringVal(">= 3.34.0"),
		})
		if featureflag.Spotinst.Enabled() {
			writeMap(requiredProvidersBody, "spotinst", map[string]cty.Value{
				"source":  cty.StringVal("spotinst/spotinst"),
				"version": cty.StringVal(">= 1.33.0"),
			})
		}
	}

	bytes := hclwrite.Format(f.Bytes())
	t.files["kubernetes.tf"] = bytes

	return nil
}

// writeLocalsOutputs creates the locals block and output blocks for all output variables
// Example:
// locals {
//   key1 = "value1"
//   key2 = "value2"
// }
// output "key1" {
//   value = "value1"
// }
// output "key2" {
//   value = "value2"
// }
func writeLocalsOutputs(body *hclwrite.Body, outputs map[string]*terraformOutputVariable) error {
	if len(outputs) == 0 {
		return nil
	}

	localsBlock := body.AppendNewBlock("locals", []string{})
	body.AppendNewline()
	// each output is added to a single locals block and its own output block
	localsBody := localsBlock.Body()
	existingOutputVars := make(map[string]bool)

	outputNames := make([]string, 0, len(outputs))
	for k := range outputs {
		outputNames = append(outputNames, k)
	}
	sort.Slice(outputNames, func(i, j int) bool {
		return tfSanitize(outputs[outputNames[i]].Key) < tfSanitize(outputs[outputNames[j]].Key)
	})

	for _, n := range outputNames {
		v := outputs[n]
		tfName := tfSanitize(v.Key)
		outputBlock := body.AppendNewBlock("output", []string{tfName})
		outputBody := outputBlock.Body()
		if v.Value != nil {
			writeLiteral(outputBody, "value", v.Value)
			writeLiteral(localsBody, tfName, v.Value)
		} else {
			deduped, err := DedupLiterals(v.ValueArray)
			if err != nil {
				return err
			}
			writeLiteralList(outputBody, "value", deduped)
			writeLiteralList(localsBody, tfName, deduped)
		}

		if existingOutputVars[tfName] {
			return fmt.Errorf("duplicate variable found: %s", tfName)
		}
		existingOutputVars[tfName] = true
		body.AppendNewline()
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"net/http"
	"Utils"
	"Flags"
	"path/filepath"
	"strconv"
	"html/template"
	"UserManager"
	"SessionManager"
)

const (
	rootURL = "/"
	docURL = "/doc/"

	debugging = true; // Disables Login for Debugging
)

func main() {
	requestMultiplexer := http.NewServeMux()

	// General Handlers for Website + Godoc
	requestMultiplexer.HandleFunc(docURL, docHandler)
	requestMultiplexer.HandleFunc(rootURL, sessionCheckHandler)

	//Login,Logout
	requestMultiplexer.HandleFunc(rootURL + "login", authHandler)
	requestMultiplexer.HandleFunc(rootURL + "logout", authHandler)

	cfg := &tls.Config{
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		PreferServerCipherSuites: true,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		},
	}

	srv := &http.Server{
		Addr:         ":" + strconv.Itoa(Flags.GetPort()),
		Handler:      requestMultiplexer,
		TLSConfig:    cfg,
		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
	}

	Utils.HandlePanic(srv.ListenAndServeTLS(Flags.GetTLScert(), Flags.GetTLSkey()))
}

func sessionCheckHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	r.ParseForm()
	session := r.FormValue("session")

	if (SessionManager.ValidateSession(session) || debugging || r.URL.Path[1:] == "login.html") {
		rootHandler(w, r)
		return
	} else {
		http.Redirect(w, r, "/login.html", 302)
		return
	}
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	var url string
	if r.URL.Path[1:] == "" {
		url = "index.html"
	} else {
		url = r.URL.Path[1:]
	}

	path, err := filepath.Abs("res/html/" + url)
	Utils.HandlePrint(err)

	t, err := template.ParseFiles(path)
	Utils.HandlePrint(err)
	files, err := filepath.Glob("*")
	Utils.HandlePrint(err)

	Utils.LogDebug("File Accessed:	" + path)
	t.Execute(w, files)
}

func authHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	intent := r.FormValue("intent")
	if (intent == "login") {
		email := r.FormValue("email")
		password := r.FormValue("password")
		if (UserManager.VerifyUser(email, password)) {
			http.Redirect(w, r, "/index.html", 302)
		} else {
			http.Redirect(w, r, "/login.html?status=failed", 302)
		}
	} else if (intent == "logout") {
		session := r.FormValue("session")
		err := SessionManager.InvalidateSession(session)
		if (err != nil) {
			http.Redirect(w, r, "/login.html?status=error", 302)
			return
		}
		http.Redirect(w, r, "/login.html?status=logout", 302)
		return
	} else {
		http.Redirect(w, r, "/login.html?status=badrequest", 302)
		return
	}
}

func docHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	path, err := filepath.Abs("res/" + r.URL.Path[1:])
	Utils.HandlePrint(err)

	Utils.LogDebug("File Accessed:	" + path)

	if (r.URL.Path[1:] == "doc/" || r.URL.Path[1:] == "doc") {
		Utils.LogDebug("Redirecting from doc to doc/pkg/fileServer.html")
		http.Redirect(w, r, "pkg/FileServer.html", 300)
	} else {
		http.ServeFile(w, r, path)
	}
}

<commit_msg>Static File Serve Fix<commit_after>package main

import (
	"crypto/tls"
	"net/http"
	"Utils"
	"Flags"
	"path/filepath"
	"strconv"
	"html/template"
	"UserManager"
	"SessionManager"
)

const (
	rootURL = "/"
	docURL = "/doc/"

	debugging = true; // Disables Login for Debugging
)

func main() {
	requestMultiplexer := http.NewServeMux()

	// General Handlers for Website + Godoc
	requestMultiplexer.HandleFunc(docURL, docHandler)
	requestMultiplexer.HandleFunc(rootURL, sessionCheckHandler)

	//Login,Logout
	requestMultiplexer.HandleFunc(rootURL + "login", authHandler)
	requestMultiplexer.HandleFunc(rootURL + "logout", authHandler)

	cfg := &tls.Config{
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		PreferServerCipherSuites: true,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		},
	}

	srv := &http.Server{
		Addr:         ":" + strconv.Itoa(Flags.GetPort()),
		Handler:      requestMultiplexer,
		TLSConfig:    cfg,
		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
	}

	Utils.HandlePanic(srv.ListenAndServeTLS(Flags.GetTLScert(), Flags.GetTLSkey()))
}

func sessionCheckHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	r.ParseForm()
	session := r.FormValue("session")

	if (SessionManager.ValidateSession(session) || debugging || r.URL.Path[1:] == "login.html") {
		rootHandler(w, r)
		return
	} else {
		http.Redirect(w, r, "/login.html", 302)
		return
	}
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	var url string
	if r.URL.Path[1:] == "" {
		url = "index.html"
	} else {
		url = r.URL.Path[1:]
	}

	path, err := filepath.Abs("res/html/" + url)
	Utils.HandlePrint(err)

	if(url[len(url)-4:]=="html"){
		t, err := template.ParseFiles(path)
		Utils.HandlePrint(err)
		files, err := filepath.Glob("*")
		Utils.HandlePrint(err)

		Utils.LogDebug("File Accessed with TemplateEngine:	" + path)
		t.Execute(w, files)
	}else{
		http.ServeFile(w,r,path)
		Utils.LogDebug("File Accessed with StaticFileServer:	" + path)
	}

}

func authHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	intent := r.FormValue("intent")
	if (intent == "login") {
		email := r.FormValue("email")
		password := r.FormValue("password")
		if (UserManager.VerifyUser(email, password)) {
			http.Redirect(w, r, "/index.html", 302)
		} else {
			http.Redirect(w, r, "/login.html?status=failed", 302)
		}
	} else if (intent == "logout") {
		session := r.FormValue("session")
		err := SessionManager.InvalidateSession(session)
		if (err != nil) {
			http.Redirect(w, r, "/login.html?status=error", 302)
			return
		}
		http.Redirect(w, r, "/login.html?status=logout", 302)
		return
	} else {
		http.Redirect(w, r, "/login.html?status=badrequest", 302)
		return
	}
}

func docHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

	path, err := filepath.Abs("res/" + r.URL.Path[1:])
	Utils.HandlePrint(err)

	Utils.LogDebug("File Accessed:	" + path)

	if (r.URL.Path[1:] == "doc/" || r.URL.Path[1:] == "doc") {
		Utils.LogDebug("Redirecting from doc to doc/pkg/fileServer.html")
		http.Redirect(w, r, "pkg/FileServer.html", 300)
	} else {
		http.ServeFile(w, r, path)
	}
}

<|endoftext|>