Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation for app configuration #3

Merged
merged 7 commits into from
Dec 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# 💬 Epigram
# 💬 Epigram

> **[ep·i·gram][wikipedia]** *noun*
: a pithy saying or remark expressing an idea in a clever and amusing way.
> **[ep·i·gram][wikipedia]** _noun_
> : a pithy saying or remark expressing an idea in a clever and amusing way.

Epigram is a simple web service for communities to immortalize the enlightening, funny, or downright dumb quotes that they hear.


[wikipedia]: https://en.wikipedia.org/wiki/Epigram

## Features
Expand Down Expand Up @@ -33,18 +32,10 @@ go install github.com/willbicks/epigram@latest

Alternatively, Docker container images are available at [ghcr.io/willbicks/epigram](https://ghcr.io/willbicks/epigram).

### Configuration

While most configuration parameters have a default value,

Epigram expects a `config.yml` in the same directory as it is run, or at the location specified by the `EP_CONFIG` environment variable.

TODO: Explain config file contents. For more information, see [Configuration](docs/config.md).

## Documentation

- [Project Structure / Architecture](docs/structure.md)
- [Configuration](docs/config.md)
- [Project Structure / Architecture](docs/structure.md)

## Contributing

Expand Down
22 changes: 16 additions & 6 deletions config-ex.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
port: 8080
bind: 0.0.0.0
address: 127.0.0.1

baseURL: http://localhost:8080

title: Epigram Demo
description: A place to record quotes.
database: inmemory

repo: inmemory

trustProxy: false
googleOIDC:
clientId: ""
clientSecret: ""

OIDCProvider:
name: google
issuerURL: "https://accounts.google.com"
clientId: "1234567890.apps.googleusercontent.com"
clientSecret: "your-client-secret"

entryQuestions:
- question: The best color
- question: What is the best color?
answer: purple
- question: What is the best animal?
answer: dog
76 changes: 74 additions & 2 deletions docs/config.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,77 @@
# Configuration

The Epigram server can be configured using two methods; a YAML configuration file, and environment variables.
The Epigram server is primarily configured by a YAML file, while certain parameters can be overwritten by environment variables.

TODO: Add additional description and table of config parameters. For now, see the godocs for [internal/config](../internal/config).
On Windows, the default configuration file location is `.\config.yml`, while on Linux, the default location is `/etc/epigram/config.yml`. On both platforms, the config file location can be overwritten by the `EP_CONFIG` environment variable.

## Configuration 'Merging'

Many configuration parameters can be set from multiple sources. The following list outlines the order in which configuration parameters are merged. If a parameter is specified by multiple sources, the value from the last source (highest number) will be used.

1. Default values
2. Configuration file
3. Environment variables

## Configuration parameters

The following table outlines parameters which can be configured, as well as their corresponding environment variables (if applicable), and their default values.

| Parameter | YAML key | Environment variable | Default value |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **Address** to listen for incoming requests. on. | `address` | `EP_ADDRESS` | 0.0.0.0 |
| **Port** to listen for incoming requests. on. | `port` | `EP_PORT` | 80 |
| **BaseURL** is the complete domain and path to access the root of the web server, used for creating callback URLs. | `baseURL` | `EP_BASEURL` | |
| **Title** is the name of the application to be shown in the frontend. | `title` | `EP_TITLE` | Epigram |
| **Description** is a short description of the application to be shown in the frontend. | `description` | `EP_DESCRIPTION` | Epigram is a simple web service for communities to immortalize the enlightening, funny, or downright dumb quotes that they hear. |
| **Repo** dictates what type of storage the application should use for data persistence. (either 'inmemory' or 'sqlite') | `repo` | `EP_REPO` | inmemory |
| **DBLoc** is the location where the database can be found. In the case of an SQLite repository, this is the path to database file. It has no effect on an in-memory repository. | `DBLoc` | `EP_DBLOC` | Unix: `/var/epigram/epigram.db`, Windows: `.\epigram.db` |
| **TrustProxy** dictates whether `X-Forwarded-For` header should be trusted to obtain the client IP, or if the requestor IP should be used instead. | `trustProxy` | `EP_TRUSTPROXY` | false |

### OIDC Provider Configuration

Additionally, an OpenID Connect provider is required to authenticate users. The following parameters are required to configure the OIDC provider. These parameters cannot be set via environment variables, and have no default values. They should be specified in the configuration file as a map under the `OIDCProvider` key.

| Parameter | YAML key | Example value |
| ---------------------------------------------------------------- | -------------- | ------------------------------------- |
| **Name** of the OIDC provider, used to build it's callback URL. | `name` | google |
| **IssuerURL** of the OIDC provider. | `issuerURL` | https://accounts.google.com |
| **ClientID** assigned by the OIDC provider. | `clientID` | 1234567890.apps.googleusercontent.com |
| **ClientSecret** used to authenticate against the OIDC provider. | `clientSecret` | your-client-secret |

### Entry Quiz Configuration

The entry quiz is a simple quiz which is presented to users when they first visit the site. These parameters cannot be set via environment variables, and have no default values. They should be specified in the configuration file as a sequence of maps under the `entryQuiz` key.

| Parameter | YAML key | Example value |
| ------------------------------------- | ---------- | ----------------------- |
| **Question** to be asked to the user. | `question` | What is the best color? |
| **Answer** to the question. | `answer` | purple |

## Example Configuration

```yaml
port: 8080
address: 127.0.0.1

baseURL: http://localhost:8080

title: Epigram Demo
description: A place to record quotes.

repo: SQLite
DBLoc: ./epigram.db

trustProxy: false

OIDCProvider:
name: google
issuerURL: "https://accounts.google.com"
clientId: "1234567890.apps.googleusercontent.com"
clientSecret: "your-client-secret"

entryQuestions:
- question: What is the best color?
answer: purple
- question: What is the best animal?
answer: dog
```
34 changes: 14 additions & 20 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package config

import (
"os"
"path"
"strings"
)

Expand Down Expand Up @@ -40,33 +39,33 @@ type OIDCProvider struct {
ClientSecret string `yaml:"clientSecret"`
}

// EntryQuestion is a question the user must answer before being granted entrance to the applicaiton
// EntryQuestion is a question the user must answer before being granted entrance to the application
type EntryQuestion struct {
Question string `yaml:"question"`
Answer string `yaml:"answer"`
}

// Application represents the root configuration struct for the server.
type Application struct {
// Address is an IP address (or hostname) to bind the server to
// Address is an IP address (or hostname) to bind the server to.
Address string
// Port is the port to be bound to on the specified address
// Port is the port to be bound to on the specified address.
Port uint16
// BaseURL is the complete domain and path to access the root of the web server, used for creating callback URLs
// BaseURL is the complete domain and path to access the root of the web server, used for creating callback URLs.
BaseURL string `yaml:"baseURL"`
// Title is the name of the applicaiton used in the frontend
// Title is the name of the application to be shown in the frontend.
Title string `yaml:"title"`
// Description is a subtutle shown in the frontend
// Description is a short description of the application to be shown in the frontend.
Description string `yaml:"description"`
// Repo dictates what type of storage the application should user for data persistence.
// Repo dictates what type of storage the application should use for data persistence.
Repo Repository `yaml:"repo"`
// DBLoc is the location where the database can be found. In the case of an SQLite repository, this is the path to database file.
DBLoc string `yaml:"DBLoc"`
// TrustProxy dictates whether X-Forwarded-For header should be trusted to obtain the client IP, or if the requestor IP shoud be used instead
// TrustProxy dictates whether the `X-Forwarded-For` header should be trusted to obtain the client IP, or if the requestor IP should be used instead.
TrustProxy bool `yaml:"trustProxy"`
// OIDCProvider is the OIDC provider used to authenticate users
// OIDCProvider is the OIDC provider used to authenticate users.
OIDCProvider OIDCProvider `yaml:"OIDCProvider"`
// EntryQuestions is an array of questions
// EntryQuestions is an array of questions.
EntryQuestions []EntryQuestion `yaml:"entryQuestions"`
}

Expand Down Expand Up @@ -110,28 +109,23 @@ func (base Application) merge(layer Application) Application {
// Parse layers three config sources to return the final application configuration. First, the default configuration is
// loaded (which varied based on system operating system). Then, a .yml configuration file is loaded. If the EP_CONFIG
// env var is set, the yml file is loaded from there, otherwise, it is loaded from the default config location (again,
//varies based on os, see default files for more). Finally, any configuration parameters specified in environment
// varies based on os, see default files for more). Finally, any configuration parameters specified in environment
// variables are applied, and the resulting Application config is returned.
func Parse() (Application, error) {
// Load default configuration
cfg := Default

// Merge YAML configuration
ymlPath := path.Join(configDir, "config.yml")
if p := getEnvVar("config"); p != "" {
ymlPath = p
}

ymlBytes, err := os.ReadFile(ymlPath)
ymlBytes, err := os.ReadFile(getConfigLoc())
if err != nil {
return Application{}, err
}

layer, err := parseYAML(ymlBytes)
yamlConfig, err := parseYAML(ymlBytes)
if err != nil {
return Application{}, err
}
cfg = cfg.merge(layer)
cfg = cfg.merge(yamlConfig)

// Merge environment configuration
cfg = cfg.merge(fromEnvironment())
Expand Down
10 changes: 10 additions & 0 deletions internal/config/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ package config

import (
"os"
"path"
"strconv"
"strings"
)

// EnvironmentPrefix is a string that is prefixed to environment variables seperated by an underscore.
const EnvironmentPrefix string = "EP"

// getConfigLoc returns the location of the config file. If the Config environment variable is set, it will be used.
// Otherwise, the default location will be configDir/config.yml.
func getConfigLoc() string {
if p := getEnvVar("Config"); p != "" {
return p
}
return path.Join(configDir, "config.yml")
}

// getEnvVar returns the environment variable labeled with the specified name (converted to uppercase), using the
// specified EnvironmentPrefix.
func getEnvVar(name string) string {
Expand Down