Documentation
¶
Overview ¶
Package manifest reads and checks aicoded.yaml, the app's permission list.
Runners, the delivery pipeline and the aicoded CLI import it. App code may not: E-LINT-001 refuses the import.
Index ¶
- Constants
- Variables
- func Canonical(m Manifest) []byte
- func ClassRank(class string) (int, bool)
- func Hash(m Manifest) string
- func PlatformHost(host string) bool
- func ValidApp(name string) bool
- func ValidHost(host string) bool
- type Access
- type Audience
- type Change
- type Data
- type Egress
- type Email
- type Job
- type Manifest
- type Serve
- type Services
Examples ¶
Constants ¶
const FileName = "aicoded.yaml"
FileName is the name of the permission list in an app's directory.
Variables ¶
var Classes = []string{"public", "internal", "confidential", "personal"}
Classes are the classes of data a source may hold, from the least sensitive to the most.
var ReservedClasses = []string{"health", "hr", "card"}
ReservedClasses are the classes of data no app may hold: health, HR and card data. A permission list that names one is not allowed (E-MAN-024).
Functions ¶
func Canonical ¶ added in v0.7.0
Canonical returns the form of m that Hash hashes: its JSON encoding, keyed by the names of aicoded.yaml, with every list sorted and each item once, and with what is empty or false left out. An egress entry is its host alone when it binds no key and an object otherwise, the entries sorted by host. Two permission lists that differ only in the order of keys or of list items, or in comments, have one canonical form.
func ClassRank ¶ added in v0.7.0
ClassRank returns the place of class in Classes, and false for any other name, a reserved class included.
func Hash ¶ added in v0.7.0
Hash returns "sha256:" followed by the lowercase hex SHA-256 of Canonical(m).
func PlatformHost ¶ added in v0.10.0
PlatformHost reports whether host, in any case and with or without a dot at the end, is a host of the platform: one of its domains, such as aicoded.cloud, or a subdomain of one.
Types ¶
type Access ¶
type Access struct {
Require []string `yaml:"require" json:"require,omitzero"`
Guard bool `yaml:"guard" json:"guard,omitzero"`
Calls []string `yaml:"calls" json:"calls,omitzero"`
}
Access is one entry of the access section aicoded generate writes: the role rules a viewer must meet, whether a Guard on the page's path decides who sees each record, whether everyone the rules admit may see every record, and the page calls. Guard and Shared are never both set.
type Audience ¶
type Audience struct {
Internal []string `yaml:"internal" json:"internal,omitzero"`
External []string `yaml:"external" json:"external,omitzero"`
}
Audience is who may use the app: people of the company, as group:<name> or everyone, and outside people, as idp:<name> or magic-link.
type Change ¶
type Change struct{ Field, Key, Before, After string }
Change is one difference between two permission lists. Key names the entry of a keyed field and is empty otherwise; Before is empty for what was added, After for what was removed.
func Diff ¶
Diff returns the differences between before and after, nil when there are none.
The changes come in the order of the fields, then by key. A list, such as egress, changes by each item removed or added, removals first, each sorted; a new order is no change, and a field left out equals an empty one. A keyed entry changes as a whole, written as text: a data source's classes, sorted; an access path's "require <rules>", then "; guard" or "; shared", then "; calls <names>", when they apply, its lists in written order; a served function's "callers <apps>", then "; require <rules>" and "; apps" when they apply, its lists sorted; a job's cron. Egress changes by each host removed or added, then by each key binding, keyed by its host: "secret <name> in <header>", then ` after "<prefix>"` with a prefix, and empty for a host without a key. The functions called from another app change one by one, keyed by that app.
Example ¶
Diff lists what changed between two permission lists, field by field, as the platform's change records show it. A new order of a list is no change.
package main
import (
"fmt"
"aicoded.dev/framework/manifest"
)
func main() {
before := manifest.Manifest{
App: "rooms",
Access: map[string]manifest.Access{"/rooms/{id}": {Require: []string{"staff"}}},
Settings: []string{"greeting", "report_day"},
}
after := manifest.Manifest{
App: "rooms",
Access: map[string]manifest.Access{"/rooms/{id}": {Require: []string{"staff"}, Guard: true}},
Egress: []manifest.Egress{{Host: "api.partner.example"}},
Settings: []string{"report_day", "greeting"},
}
for _, c := range manifest.Diff(before, after) {
fmt.Printf("%+v\n", c)
}
}
Output: {Field:access Key:/rooms/{id} Before:require staff After:require staff; guard} {Field:egress Key: Before: After:api.partner.example}
type Data ¶
type Data struct {
Source string `yaml:"source" json:"source,omitzero"`
Classes []string `yaml:"classes" json:"classes,omitzero"`
}
Data is one source of data the app reaches: sqldb, filestore:<name> or connector:<name>, with the classes of data it holds.
type Egress ¶ added in v0.10.0
type Egress struct {
Host string `yaml:"host" json:"host"`
Secret string `yaml:"secret" json:"secret,omitzero"`
Header string `yaml:"header" json:"header,omitzero"`
Prefix string `yaml:"prefix" json:"prefix,omitzero"`
}
Egress is one entry of egress: the host of an outside service the app calls, and, when the entry binds a key, the secret the runner adds to every request to the host, in the header Header after Prefix. In aicoded.yaml, and in JSON, a plain entry is the host alone and a binding is a mapping of host, secret, header and prefix.
func (Egress) MarshalJSON ¶ added in v0.10.0
MarshalJSON writes a plain entry as its host and a binding as an object.
func (*Egress) UnmarshalJSON ¶ added in v0.10.0
UnmarshalJSON reads what MarshalJSON writes.
type Email ¶
type Email struct {
From string `yaml:"from" json:"from,omitzero"`
ToDomains []string `yaml:"to_domains" json:"to_domains,omitzero"`
}
Email is the address the app sends mail from and the domains it may send to.
type Job ¶
type Job struct {
Cron string `yaml:"cron" json:"cron,omitzero"`
Name string `yaml:"job" json:"job,omitzero"`
}
Job is one scheduled job: its name and the cron expression, in UTC, of when it runs.
type Manifest ¶
type Manifest struct {
App string `yaml:"app" json:"app,omitzero"`
Class string `yaml:"class" json:"class,omitzero"`
Owner string `yaml:"owner" json:"owner,omitzero"`
Audience Audience `yaml:"audience" json:"audience,omitzero"`
Data []Data `yaml:"data" json:"data,omitzero"`
Access map[string]Access `yaml:"access" json:"access,omitzero"`
Services Services `yaml:"services" json:"services,omitzero"`
Egress []Egress `yaml:"egress" json:"egress,omitzero"`
Email *Email `yaml:"email" json:"email,omitzero"`
Schedule []Job `yaml:"schedule" json:"schedule,omitzero"`
Settings []string `yaml:"settings" json:"settings,omitzero"`
Secrets []string `yaml:"secrets" json:"secrets,omitzero"`
Modules []string `yaml:"modules" json:"modules,omitzero"`
Size string `yaml:"size" json:"size,omitzero"`
Resources string `yaml:"resources" json:"resources,omitzero"`
TTL string `yaml:"ttl" json:"ttl,omitzero"`
}
Manifest is the permission list, aicoded.yaml. A field left out or empty is not stated.
func Parse ¶
Parse checks the manifest data and returns it. Path is used only in the positions of problems. It reports every problem it finds as one coded error each: its error unwraps, with Unwrap() []error, to them, sorted by line. Data that is not one YAML document of the expected shape, or that uses an anchor, an alias or a merge key, is not checked any further.
Example ¶
Parse checks a permission list and returns its fields. A list with problems returns every problem, each with a code and its position.
package main
import (
"fmt"
"aicoded.dev/framework/manifest"
)
func main() {
m, err := manifest.Parse(manifest.FileName, []byte(`app: rooms
audience: {internal: [everyone]}
egress:
- hooks.partner.example
- {host: api.partner.example, secret: partner_api_key, header: Authorization, prefix: "Bearer "}
secrets: [partner_api_key]
`))
if err != nil {
fmt.Println(err)
return
}
fmt.Println(m.EgressHosts())
b, _ := m.Binding("api.partner.example")
fmt.Printf("%s in %s after %q\n", b.Secret, b.Header, b.Prefix)
}
Output: [api.partner.example hooks.partner.example] partner_api_key in Authorization after "Bearer "
func (Manifest) Binding ¶ added in v0.10.0
Binding returns the entry of egress that binds a key to host, and false when host is not listed or is listed without a key.
func (Manifest) BoundSecret ¶ added in v0.10.0
BoundSecret returns the entry of egress that binds the secret name, the first by host when several do, and false when none does.
func (Manifest) Connectors ¶
Connectors returns the names of the connectors the app declares, sorted.
func (Manifest) EgressHosts ¶ added in v0.10.0
EgressHosts returns the hosts of egress, sorted, each once.
func (Manifest) Roles ¶
Roles returns every role the access and services sections name, sorted, without "*".
type Serve ¶
type Serve struct {
Callers []string `yaml:"callers" json:"callers,omitzero"`
Require []string `yaml:"require,omitempty" json:"require,omitzero"`
Apps bool `yaml:"apps,omitempty" json:"apps,omitzero"`
}
Serve says who may call one function the app serves: the calling apps, the role rules a viewer must meet, written as in the access section, and whether a calling app may call it with no viewer. A function without role rules takes no call on behalf of a viewer.
type Services ¶
type Services struct {
Calls map[string][]string `yaml:"calls" json:"calls,omitzero"`
Serves map[string]Serve `yaml:"serves" json:"serves,omitzero"`
}
Services is the services section aicoded generate writes: the functions of other apps this app calls, by app, and the functions it serves to other apps, by name.