Write host template on save
This commit is contained in:
parent
5b6dbaf43e
commit
8d37f5df8d
@ -78,7 +78,7 @@ func CreateHost() func(http.ResponseWriter, *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = newHost.Save(); err != nil {
|
||||
if err = newHost.Save(false); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Host: %s", err.Error()), nil)
|
||||
return
|
||||
}
|
||||
@ -111,7 +111,7 @@ func UpdateHost() func(http.ResponseWriter, *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = hostObject.Save(); err != nil {
|
||||
if err = hostObject.Save(false); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity/certificate"
|
||||
"npm/internal/entity/hosttemplate"
|
||||
"npm/internal/entity/user"
|
||||
"npm/internal/types"
|
||||
"npm/internal/util"
|
||||
@ -56,8 +57,9 @@ type Model struct {
|
||||
IsDisabled bool `json:"is_disabled" db:"is_disabled" filter:"is_disabled,boolean"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
|
||||
// Expansions
|
||||
Certificate *certificate.Model `json:"certificate,omitempty"`
|
||||
User *user.Model `json:"user,omitempty"`
|
||||
Certificate *certificate.Model `json:"certificate,omitempty"`
|
||||
HostTemplate *hosttemplate.Model `json:"host_template,omitempty"`
|
||||
User *user.Model `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Model) getByQuery(query string, params []interface{}) error {
|
||||
@ -82,15 +84,17 @@ func (m *Model) Touch(created bool) {
|
||||
}
|
||||
|
||||
// Save will save this model to the DB
|
||||
func (m *Model) Save() error {
|
||||
func (m *Model) Save(skipConfiguration bool) error {
|
||||
var err error
|
||||
|
||||
if m.UserID == 0 {
|
||||
return fmt.Errorf("User ID must be specified")
|
||||
}
|
||||
|
||||
// Set this host as requiring reconfiguration
|
||||
m.Status = StatusReady
|
||||
if !skipConfiguration {
|
||||
// Set this host as requiring reconfiguration
|
||||
m.Status = StatusReady
|
||||
}
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = create(m)
|
||||
@ -105,7 +109,7 @@ func (m *Model) Save() error {
|
||||
func (m *Model) Delete() bool {
|
||||
m.Touch(false)
|
||||
m.IsDeleted = true
|
||||
if err := m.Save(); err != nil {
|
||||
if err := m.Save(false); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -127,5 +131,11 @@ func (m *Model) Expand(items []string) error {
|
||||
m.Certificate = &cert
|
||||
}
|
||||
|
||||
if util.SliceContainsItem(items, "hosttemplate") && m.HostTemplateID > 0 {
|
||||
var templ hosttemplate.Model
|
||||
templ, err = hosttemplate.GetByID(m.HostTemplateID)
|
||||
m.HostTemplate = &templ
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -1,13 +1,51 @@
|
||||
package nginx
|
||||
|
||||
import "npm/internal/entity/host"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"npm/internal/config"
|
||||
"npm/internal/entity/host"
|
||||
"npm/internal/logger"
|
||||
)
|
||||
|
||||
// ConfigureHost will attempt to write nginx conf and reload nginx
|
||||
func ConfigureHost(h host.Model) error {
|
||||
// nolint: errcheck, gosec
|
||||
h.Expand([]string{"certificate"})
|
||||
h.Expand([]string{"certificate", "hosttemplate"})
|
||||
|
||||
data := TemplateData{
|
||||
ConfDir: fmt.Sprintf("%s/nginx/hosts", config.Configuration.DataFolder),
|
||||
DataDir: config.Configuration.DataFolder,
|
||||
CertsDir: config.Configuration.Acmesh.CertHome,
|
||||
Host: &h,
|
||||
Certificate: h.Certificate,
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s/host_%d.conf", data.ConfDir, h.ID)
|
||||
|
||||
// Write the config to disk
|
||||
err := writeTemplate(filename, h.HostTemplate.Template, data)
|
||||
if err != nil {
|
||||
// this configuration failed somehow
|
||||
h.Status = host.StatusError
|
||||
h.ErrorMessage = fmt.Sprintf("Template generation failed: %s", err.Error())
|
||||
logger.Debug(h.ErrorMessage)
|
||||
return h.Save(true)
|
||||
}
|
||||
|
||||
// nolint: errcheck, gosec
|
||||
reloadNginx()
|
||||
return nil
|
||||
if err := reloadNginx(); err != nil {
|
||||
// reloading nginx failed, likely due to this host having a problem
|
||||
h.Status = host.StatusError
|
||||
h.ErrorMessage = fmt.Sprintf("Nginx configuation error: %s", err.Error())
|
||||
writeConfigFile(filename, fmt.Sprintf("# %s", h.ErrorMessage))
|
||||
logger.Debug(h.ErrorMessage)
|
||||
} else {
|
||||
// All good
|
||||
h.Status = host.StatusOK
|
||||
h.ErrorMessage = ""
|
||||
logger.Debug("ConfigureHost OK: %+v", h)
|
||||
}
|
||||
|
||||
return h.Save(true)
|
||||
}
|
||||
|
86
backend/internal/nginx/template_test.go
Normal file
86
backend/internal/nginx/template_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"npm/internal/entity/certificate"
|
||||
"npm/internal/entity/host"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWriteTemplate(t *testing.T) {
|
||||
template := `
|
||||
{{#if Host.IsDisabled}}
|
||||
# Host is disabled
|
||||
{{else}}
|
||||
server {
|
||||
{{#if Certificate}}
|
||||
{{#if Certificate.CertificateAuthorityID}}
|
||||
# Acme SSL
|
||||
include {{ConfDir}}/npm/conf.d/acme-challenge.conf;
|
||||
include {{ConfDir}}/npm/conf.d/include/ssl-ciphers.conf;
|
||||
ssl_certificate {{CertsDir}}/npm-{{Certificate.ID}}/fullchain.pem;
|
||||
ssl_certificate_key {{CertsDir}}/npm-{{Certificate.ID}}/privkey.pem;
|
||||
{{else}}
|
||||
# Custom SSL
|
||||
ssl_certificate {{DataDir}}/custom_ssl/npm-{{Certificate.ID}}/fullchain.pem;
|
||||
ssl_certificate_key {{DataDir}}/custom_ssl/npm-{{Certificate.ID}}/privkey.pem;
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
}
|
||||
{{/if}}
|
||||
|
||||
`
|
||||
|
||||
type want struct {
|
||||
output string
|
||||
err error
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
data TemplateData
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "Basic Template enabled",
|
||||
data: TemplateData{
|
||||
ConfDir: "/etc/nginx/conf.d",
|
||||
Host: &host.Model{
|
||||
IsDisabled: false,
|
||||
},
|
||||
Certificate: &certificate.Model{
|
||||
CertificateAuthorityID: 0,
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
output: "\nserver {\n # Custom SSL\n ssl_certificate /custom_ssl/npm-0/fullchain.pem;\n ssl_certificate_key /custom_ssl/npm-0/privkey.pem;\n \n}\n\n",
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Basic Template disabled",
|
||||
data: TemplateData{
|
||||
ConfDir: "/etc/nginx/conf.d",
|
||||
DataDir: "/data",
|
||||
CertsDir: "/acme.sh/certs",
|
||||
Host: &host.Model{
|
||||
IsDisabled: true,
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
output: "\n # Host is disabled\n\n",
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(st *testing.T) {
|
||||
output, err := generateHostConfig(template, test.data)
|
||||
assert.Equal(t, test.want.err, err)
|
||||
assert.Equal(t, test.want.output, output)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,31 +1,46 @@
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"npm/embed"
|
||||
"npm/internal/entity/certificate"
|
||||
"npm/internal/entity/host"
|
||||
"npm/internal/logger"
|
||||
|
||||
"github.com/aymerick/raymond"
|
||||
)
|
||||
|
||||
// WriteTemplate will load, parse and write a template file
|
||||
func WriteTemplate(templateName, outputFilename string, data map[string]interface{}) error {
|
||||
// get template file content
|
||||
subFs, _ := fs.Sub(embed.NginxFiles, "nginx")
|
||||
template, err := fs.ReadFile(subFs, templateName)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Render
|
||||
parsedFile, err := raymond.Render(string(template), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write it
|
||||
// nolint: gosec
|
||||
return ioutil.WriteFile(outputFilename, []byte(parsedFile), 0644)
|
||||
// TemplateData ...
|
||||
type TemplateData struct {
|
||||
ConfDir string
|
||||
DataDir string
|
||||
CertsDir string
|
||||
Host *host.Model
|
||||
Certificate *certificate.Model
|
||||
}
|
||||
|
||||
func generateHostConfig(template string, data TemplateData) (string, error) {
|
||||
return raymond.Render(template, data)
|
||||
}
|
||||
|
||||
func writeTemplate(filename, template string, data TemplateData) error {
|
||||
output, err := generateHostConfig(template, data)
|
||||
if err != nil {
|
||||
output = fmt.Sprintf("# Template Error: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write it. This will also write an error comment if generation failed
|
||||
// nolint: gosec
|
||||
writeErr := writeConfigFile(filename, output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeErr
|
||||
}
|
||||
|
||||
func writeConfigFile(filename, content string) error {
|
||||
logger.Debug("Writing %s with:\n%s", filename, content)
|
||||
// nolint: gosec
|
||||
return ioutil.WriteFile(filename, []byte(content), 0644)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user