view cmd/gemma/geoserver.go @ 414:c1047fd04a3a

Moved project specific Go packages to new pkg folder.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 15 Aug 2018 17:30:50 +0200
parents 0b8921797064
children ff26ffc18a04
line wrap: on
line source

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"gemma.intevation.de/gemma/pkg/config"
	"gemma.intevation.de/gemma/pkg/misc"
)

const (
	workspaceName  = "gemma"
	datastoreName  = "gemma"
	databaseScheme = "waterway"
	databaseType   = "postgis"
)

const (
	startupSQL = `` // `SET SESSION AUTHORIZATION waterway_user`
	closeupSQL = `` // `RESET SESSION AUTHORIZATION`
)

func basicAuth(user, password string) func(req *http.Request) {
	auth := "Basic " + misc.BasicAuth(user, password)
	return func(req *http.Request) {
		req.Header.Add("authorization", auth)
	}
}

func asJSON(req *http.Request) {
	req.Header.Set("Content-Type", "application/json")
}

func ensureWorkspace() error {
	var (
		url      = config.GeoServerURL()
		user     = config.GeoServerUser()
		password = config.GeoServerPassword()
		auth     = basicAuth(user, password)
	)

	// Probe  workspace.
	req, err := http.NewRequest(
		http.MethodGet,
		url+"/rest/workspaces/"+workspaceName+".json",
		nil)
	if err != nil {
		return err
	}
	auth(req)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusNotFound {
		log.Println("info: workspace " + workspaceName + " already exists.")
		return nil
	}

	// Create workspace

	log.Println("info: creating workspace " + workspaceName)

	const createJSON = `{"workspace":{"name":"` + workspaceName + `"}}`

	req, err = http.NewRequest(
		http.MethodPost,
		url+"/rest/workspaces",
		bytes.NewReader([]byte(createJSON)))
	if err != nil {
		return err
	}
	auth(req)
	asJSON(req)
	if resp, err = http.DefaultClient.Do(req); err != nil {
		return err
	}

	if resp.StatusCode != http.StatusCreated {
		err = fmt.Errorf("Status code '%s' (%d)",
			http.StatusText(resp.StatusCode),
			resp.StatusCode)
	}

	return err
}

func ensureDataStore() error {
	var (
		url      = config.GeoServerURL()
		user     = config.GeoServerUser()
		password = config.GeoServerPassword()
		auth     = basicAuth(user, password)
	)

	// Probe datastore.
	req, err := http.NewRequest(
		http.MethodGet,
		url+"/rest/workspaces/"+workspaceName+"/datastores/"+datastoreName+".json",
		nil)
	if err != nil {
		return err
	}
	auth(req)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusNotFound {
		log.Println("info: datastore " + datastoreName + " already exists.")
		return nil
	}

	// Create datastore.
	log.Println("info: creating datastore " + datastoreName)

	type entry struct {
		Key   interface{} `json:"@key"`
		Value interface{} `json:"$"`
	}

	// Create datastore.
	ds := map[string]interface{}{
		"dataStore": map[string]interface{}{
			"name": datastoreName,
			"connectionParameters": map[string]interface{}{
				"entry": []entry{
					{"host", config.DBHost()},
					{"port", config.DBPort()},
					{"database", config.DBName()},
					{"schema", databaseScheme},
					{"user", config.SysAdmin()},
					{"passwd", config.SysAdminPassword()},
					{"dbtype", databaseType},
					{"Session startup SQL", startupSQL},
					{"Session close-up SQL", closeupSQL},
				},
			},
		},
	}
	var out bytes.Buffer
	enc := json.NewEncoder(&out)
	if err := enc.Encode(&ds); err != nil {
		return err
	}

	req, err = http.NewRequest(
		http.MethodPost,
		url+"/rest/workspaces/"+workspaceName+"/datastores",
		bytes.NewReader(out.Bytes()))
	if err != nil {
		return err
	}
	auth(req)
	asJSON(req)
	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusCreated {
		err = fmt.Errorf("Status code '%s' (%d)",
			http.StatusText(resp.StatusCode),
			resp.StatusCode)
	}

	return err
}

func ensureFeatures() error {
	var (
		url      = config.GeoServerURL()
		user     = config.GeoServerUser()
		password = config.GeoServerPassword()
		auth     = basicAuth(user, password)
		tables   = config.GeoServerTables()
	)

	if len(tables) == 0 {
		log.Println("info: no tables to publish")
		return nil
	}

	log.Printf("info: number of tables to publish %d\n", len(tables))

	var features struct {
		FeatureTypes struct {
			FeatureType []struct {
				Name string `json:"name"`
			} `json:"featureType"`
		} `json:"featureTypes"`
	}

	hasFeature := func(name string) bool {
		for _, ft := range features.FeatureTypes.FeatureType {
			if ft.Name == name {
				return true
			}
		}
		return false
	}

	// Fetch all featuretypes.
	req, err := http.NewRequest(
		http.MethodGet,
		url+"/rest/workspaces/"+workspaceName+
			"/datastores/"+datastoreName+
			"/featuretypes.json",
		nil)
	if err != nil {
		return err
	}
	auth(req)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	err = json.NewDecoder(resp.Body).Decode(&features)
	resp.Body.Close()
	if err != nil {
		// XXX: Quirk in the JSON return by GeoServer:
		// If there are no features in the datastore
		// featureType deserializes to an empty string ""
		// instead of an empty array *palmface*.
		// So assume there no features.
		hasFeature = func(string) bool { return false }
	}

	for _, table := range tables {
		if hasFeature(table) {
			log.Printf("info: featuretype %s already exists.\n", table)
			continue
		}

		// Create featuretype.
		log.Printf("info: creating featuretype %s.\n", table)

		// Create featuretype
		ft := map[string]interface{}{
			"featureType": map[string]interface{}{
				"name":       table,
				"nativeName": table,
				"title":      table,
			},
		}

		var out bytes.Buffer
		enc := json.NewEncoder(&out)
		if err := enc.Encode(&ft); err != nil {
			return err
		}

		req, err := http.NewRequest(
			http.MethodPost,
			url+"/rest/workspaces/"+workspaceName+
				"/datastores/"+datastoreName+
				"/featuretypes",
			bytes.NewReader(out.Bytes()))
		if err != nil {
			return err
		}
		auth(req)
		asJSON(req)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			return err
		}

		if resp.StatusCode != http.StatusCreated {
			return fmt.Errorf("Status code '%s' (%d)",
				http.StatusText(resp.StatusCode),
				resp.StatusCode)
		}
	}

	return nil
}

func prepareGeoServer() error {

	if config.GeoServerURL() == "" {
		log.Println("info: No tables to publish on GeoServer")
		return nil
	}

	if err := ensureWorkspace(); err != nil {
		return err
	}

	if err := ensureDataStore(); err != nil {
		return err
	}

	// TODO: Styles

	return ensureFeatures()
}