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 API types for configuration metadata #14132

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2475,3 +2475,8 @@ for a project, the pool is excluded from `lxc storage list` in that project.

Adds a new {config:option}`instance-miscellaneous:ubuntu_pro.guest_attach` configuration option for instances.
When set to `on`, if the host has guest attachment enabled, the guest can request a guest token for Ubuntu Pro via `devlxd`.

## `metadata_configuration_entity_types`

This adds entity type metadata to `GET /1.0/metadata/configuration`.
The entity type metadata is a JSON object under the `entities` key.
108 changes: 106 additions & 2 deletions doc/rest-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2774,6 +2774,111 @@ definitions:
title: InstancesPut represents the fields available for a mass update.
type: object
x-go-package: github.com/canonical/lxd/shared/api
MetadataConfiguration:
properties:
configs:
additionalProperties:
additionalProperties:
$ref: '#/definitions/MetadataConfigurationConfigKeys'
type: object
description: Configs contains all server configuration metadata.
type: object
x-go-name: Configs
entities:
additionalProperties:
$ref: '#/definitions/MetadataConfigurationEntity'
description: |-
Entities contains all authorization related metadata.

API extension: metadata_configuration_entity_types
type: object
x-go-name: Entities
title: MetadataConfiguration contains metadata about the LXD server configuration options.
type: object
x-go-package: github.com/canonical/lxd/shared/api
MetadataConfigurationConfigKey:
properties:
condition:
description: Condition describes conditions under which the configuration key can be applied.
example: Virtual machines only.
type: string
x-go-name: Condition
defaultdesc:
description: DefaultDescription contains a description of the configuration key.
example: A general description of a configuration key.
type: string
x-go-name: DefaultDescription
longdesc:
description: LongDescription contains a long-form description of the configuration key.
example: A much more in-depth description of the configuration key, including where and how it is used.
type: string
x-go-name: LongDescription
managed:
description: Managed describes whether the configuration key is managed by LXD.
example: yes.
type: string
x-go-name: Managed
required:
description: Required describes conditions under which the configuration key is required.
example: On device creation.
type: string
x-go-name: Required
shortdesc:
description: ShortDescription contains a short-form description of the configuration key.
example: A key for doing X.
type: string
x-go-name: ShortDescription
type:
description: Type describes the type of the key.
example: Comma delimited CIDR format subnets.
type: string
x-go-name: Type
title: MetadataConfigurationConfigKey contains metadata about a LXD server configuration option.
type: object
x-go-package: github.com/canonical/lxd/shared/api
MetadataConfigurationConfigKeys:
properties:
keys:
items:
additionalProperties:
$ref: '#/definitions/MetadataConfigurationConfigKey'
type: object
type: array
x-go-name: Keys
title: MetadataConfigurationConfigKeys contains metadata about LXD server configuration options.
type: object
x-go-package: github.com/canonical/lxd/shared/api
MetadataConfigurationEntity:
properties:
entitlements:
description: Entitlements contains a list of entitlements that apply to a specific entity type.
items:
$ref: '#/definitions/MetadataConfigurationEntityEntitlement'
type: array
x-go-name: Entitlements
project_specific:
description: ProjectSpecific indicates whether the entity is project specific.
example: true
type: boolean
x-go-name: ProjectSpecific
title: MetadataConfigurationEntity contains metadata about LXD server entities and available entitlements for authorization.
type: object
x-go-package: github.com/canonical/lxd/shared/api
MetadataConfigurationEntityEntitlement:
properties:
description:
description: Description describes the entitlement.
example: Grants permission to do X, Y, and Z.
type: string
x-go-name: Description
name:
description: Name contains the name of the entitlement.
example: can_edit
type: string
x-go-name: Name
title: MetadataConfigurationEntityEntitlement contains metadata about a LXD server entitlement.
type: object
x-go-package: github.com/canonical/lxd/shared/api
Network:
description: Network represents a LXD network
properties:
Expand Down Expand Up @@ -11381,8 +11486,7 @@ paths:
description: Sync response
properties:
metadata:
description: The generated metadata configuration
type: string
$ref: '#/definitions/MetadataConfiguration'
status:
description: Status description
example: Success
Expand Down
21 changes: 20 additions & 1 deletion lxd/auth/generate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"strings"
"unicode"

"github.com/canonical/lxd/shared/api"
"github.com/canonical/lxd/shared/entity"
"github.com/canonical/lxd/shared/logger"
)
Expand Down Expand Up @@ -92,7 +93,25 @@ func main() {
return fmt.Errorf("Failed to close OpenFGA model file: %w", err)
}

err = json.NewEncoder(os.Stdout).Encode(entityToEntitlements)
metadata := make(map[string]api.MetadataConfigurationEntity)
for eType, entitlements := range entityToEntitlements {
projectSpecific, _ := eType.RequiresProject()

apiEntitlements := make([]api.MetadataConfigurationEntityEntitlement, 0, len(entitlements))
for _, e := range entitlements {
apiEntitlements = append(apiEntitlements, api.MetadataConfigurationEntityEntitlement{
Name: e.Relation,
Description: e.Description,
})
}

metadata[string(eType)] = api.MetadataConfigurationEntity{
ProjectSpecific: projectSpecific,
Entitlements: apiEntitlements,
}
}

err = json.NewEncoder(os.Stdout).Encode(metadata)
if err != nil {
return fmt.Errorf("Failed to write entitlement json to stdout: %w", err)
}
Expand Down
6 changes: 3 additions & 3 deletions lxd/documentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"

"github.com/canonical/lxd/lxd/response"
"github.com/canonical/lxd/shared/api"
)

var metadataConfigurationCmd = APIEndpoint{
Expand Down Expand Up @@ -46,8 +47,7 @@ var generatedDoc embed.FS
// description: Status code
// example: 200
// metadata:
// type: string
// description: The generated metadata configuration
// $ref: "#/definitions/MetadataConfiguration"
// "403":
// $ref: "#/responses/Forbidden"
// "500":
Expand All @@ -58,7 +58,7 @@ func metadataConfigurationGet(d *Daemon, r *http.Request) response.Response {
return response.SmartError(err)
}

var data map[string]any
var data api.MetadataConfiguration
err = json.Unmarshal(file, &data)
if err != nil {
return response.SmartError(err)
Expand Down
52 changes: 14 additions & 38 deletions lxd/lxd-metadata/lxd_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import (
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"

"github.com/canonical/lxd/shared"
"github.com/canonical/lxd/shared/api"

"gopkg.in/yaml.v2"
)
Expand All @@ -43,37 +43,6 @@ type doc struct {
Entities json.RawMessage `json:"entities"`
}

// detectType detects the type of a string and returns the corresponding value.
func detectType(s string) any {
i, err := strconv.Atoi(s)
if err == nil {
return i
}

b, err := strconv.ParseBool(s)
if err == nil {
return b
}

f, err := strconv.ParseFloat(s, 64)
if err == nil {
return f
}

t, err := time.Parse(time.RFC3339, s)
if err == nil {
return t
}

// special characters handling
if s == "-" {
return ""
}

// If all conversions fail, it's a string
return s
}

// sortConfigKeys alphabetically sorts the entries by key (config option key) within each config group in an entity.
func sortConfigKeys(allEntries map[string]map[string]map[string][]any) {
for _, entityValue := range allEntries {
Expand Down Expand Up @@ -285,7 +254,7 @@ func parse(path string, outputJSONPath string, excludedPaths []string, substitut
continue
}

configKeyEntry[metadataMap["key"]].(map[string]any)[dataKVMatch[1]] = detectType(dataKVMatch[2])
configKeyEntry[metadataMap["key"]].(map[string]any)[dataKVMatch[1]] = dataKVMatch[2]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this change.

Where is the value in dataKVMatch[2] coming from?

As for how these are classified, whilst I agree that all config values are strings, my understanding of the metadata definition here is in order to indicate in the docs what sort of string values are allowed for a particular config key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like dataKVMatch is coming from this regular expression (?m)([\S]+):[\s]+([\S \"\']+) on the key value section of the lxdmeta:generate section of the doc block e.g.:

type: string
defaultdesc: `auto`
liveupdate: no
shortdesc: What to do when evacuating the instance

In this example, the first dataKVMatch outputted from lxdDocDataRegex.FindAllStringSubmatch(data, -1) would have

dataKVMatch[1] -> "type"
dataKVMatch[2] -> "string"

My feeling is that lxd-generate shouldn't be too clever about figuring out which config keys are which type. We should just include that in the description of the key. There is only one config key for which this appears to work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is only one config key for which this appears to work.
which one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit directly after this change shows the result of make update-metadata. Only one field is changed: 2614bb5#diff-d3459341f992192c3b18b6130aa7e165d305183794a84f219c7ab7512b1b31bcL4316

It is changing the value of defaultdesc from a boolean true to a string "true".

}

// There can be multiple entities for a given group
Expand Down Expand Up @@ -340,6 +309,13 @@ func parse(path string, outputJSONPath string, excludedPaths []string, substitut
return nil, fmt.Errorf("Error while marshaling project documentation: %v", err)
}

// Validate that what we've generated is valid against our API definition.
var metadataConfiguration api.MetadataConfiguration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice touch

err = json.Unmarshal(data, &metadataConfiguration)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshal generated metadata into MetadataConfiguration API type: %w", err)
}

if outputJSONPath != "" {
buf := bytes.NewBufferString("")
_, err = buf.Write(data)
Expand Down Expand Up @@ -494,7 +470,7 @@ func writeDocFile(inputJSONPath, outputTxtPath string) error {
}
}

entities := make(map[string][]map[string]string)
entities := make(map[string]api.MetadataConfigurationEntity)
err = json.Unmarshal(jsonDoc.Entities, &entities)
if err != nil {
return err
Expand All @@ -508,11 +484,11 @@ func writeDocFile(inputJSONPath, outputTxtPath string) error {
sort.Strings(sortedEntityNames)

for _, entityName := range sortedEntityNames {
entitlements := entities[entityName]
entity := entities[entityName]
buffer.WriteString(fmt.Sprintf("<!-- entity group %s start -->\n", entityName))
for _, entitlement := range entitlements {
buffer.WriteString(fmt.Sprintf("`%s`\n", entitlement["name"]))
buffer.WriteString(fmt.Sprintf(": %s\n\n", entitlement["description"]))
for _, entitlement := range entity.Entitlements {
buffer.WriteString(fmt.Sprintf("`%s`\n", entitlement.Name))
buffer.WriteString(fmt.Sprintf(": %s\n\n", entitlement.Description))
}

buffer.WriteString("\n")
Expand Down
Loading
Loading