feat: provide a way to list installed system extensions
```bash $ talosctl -n 172.20.0.2 get extensions NODE NAMESPACE TYPE ID VERSION NAME VERSION 172.20.0.2 runtime ExtensionStatus 000.ghcr.io-smira-gvisor-c927b54-dirty 1 gvisor 20220117.0-v1.0.0 ``` ```bash $ talosctl -n 172.20.0.2 get extensions -o yaml node: 172.20.0.2 metadata: namespace: runtime type: ExtensionStatuses.runtime.talos.dev id: 000.ghcr.io-smira-gvisor-c927b54-dirty version: 1 owner: runtime.ExtensionStatusController phase: running created: 2022-01-26T20:56:51Z updated: 2022-01-26T20:56:51Z spec: image: 000.ghcr.io-smira-gvisor-c927b54-dirty.sqsh metadata: name: gvisor version: 20220117.0-v1.0.0 author: Andrew Rynhard description: | This system extension provides gVisor using containerd's runtime handler. compatibility: talos: version: '> v0.15.0-alpha.1' ``` Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
parent
abfb258128
commit
cdb621c82e
@ -0,0 +1,79 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/machinery/extensions"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/runtime"
|
||||
)
|
||||
|
||||
// ExtensionStatusController loads extensions.yaml and updates ExtensionStatus resources.
|
||||
type ExtensionStatusController struct{}
|
||||
|
||||
// Name implements controller.Controller interface.
|
||||
func (ctrl *ExtensionStatusController) Name() string {
|
||||
return "runtime.ExtensionStatusController"
|
||||
}
|
||||
|
||||
// Inputs implements controller.Controller interface.
|
||||
func (ctrl *ExtensionStatusController) Inputs() []controller.Input {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Outputs implements controller.Controller interface.
|
||||
func (ctrl *ExtensionStatusController) Outputs() []controller.Output {
|
||||
return []controller.Output{
|
||||
{
|
||||
Type: runtime.ExtensionStatusType,
|
||||
Kind: controller.OutputExclusive,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run implements controller.Controller interface.
|
||||
func (ctrl *ExtensionStatusController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
// controller runs once, as extensions are static
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-r.EventCh():
|
||||
}
|
||||
|
||||
var cfg extensions.Config
|
||||
|
||||
if err := cfg.Read(constants.ExtensionsRuntimeConfigFile); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
// no extensions installed
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed loading extensions config: %w", err)
|
||||
}
|
||||
|
||||
for _, layer := range cfg.Layers {
|
||||
id := strings.TrimSuffix(layer.Image, ".sqsh")
|
||||
|
||||
if err := r.Modify(ctx, runtime.NewExtensionStatus(runtime.NamespaceName, id), func(res resource.Resource) error {
|
||||
*res.(*runtime.ExtensionStatus).TypedSpec() = *layer
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -187,6 +187,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
|
||||
Cmdline: procfs.ProcCmdline(),
|
||||
Drainer: drainer,
|
||||
},
|
||||
&runtimecontrollers.ExtensionStatusController{},
|
||||
&runtimecontrollers.KernelModuleConfigController{},
|
||||
&runtimecontrollers.KernelModuleSpecController{
|
||||
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
|
||||
|
@ -124,6 +124,7 @@ func NewState() (*State, error) {
|
||||
&network.TimeServerSpec{},
|
||||
&perf.CPU{},
|
||||
&perf.Memory{},
|
||||
&runtime.ExtensionStatus{},
|
||||
&runtime.KernelModuleSpec{},
|
||||
&runtime.KernelParamSpec{},
|
||||
&runtime.KernelParamDefaultSpec{},
|
||||
|
84
pkg/machinery/resources/runtime/extension_status.go
Normal file
84
pkg/machinery/resources/runtime/extension_status.go
Normal file
@ -0,0 +1,84 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/resource/meta"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/extensions"
|
||||
)
|
||||
|
||||
// ExtensionStatusType is type of Extension resource.
|
||||
const ExtensionStatusType = resource.Type("ExtensionStatuses.runtime.talos.dev")
|
||||
|
||||
// ExtensionStatus resource holds status of installed system extensions.
|
||||
type ExtensionStatus struct {
|
||||
md resource.Metadata
|
||||
spec ExtensionStatusSpec
|
||||
}
|
||||
|
||||
// ExtensionStatusSpec is the spec for system extensions.
|
||||
type ExtensionStatusSpec = extensions.Layer
|
||||
|
||||
// NewExtensionStatus initializes a ExtensionStatus resource.
|
||||
func NewExtensionStatus(namespace resource.Namespace, id resource.ID) *ExtensionStatus {
|
||||
r := &ExtensionStatus{
|
||||
md: resource.NewMetadata(namespace, ExtensionStatusType, id, resource.VersionUndefined),
|
||||
spec: ExtensionStatusSpec{},
|
||||
}
|
||||
|
||||
r.md.BumpVersion()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Metadata implements resource.Resource.
|
||||
func (r *ExtensionStatus) Metadata() *resource.Metadata {
|
||||
return &r.md
|
||||
}
|
||||
|
||||
// Spec implements resource.Resource.
|
||||
func (r *ExtensionStatus) Spec() interface{} {
|
||||
return r.spec
|
||||
}
|
||||
|
||||
func (r *ExtensionStatus) String() string {
|
||||
return fmt.Sprintf("runtime.ExtensionStatus.(%q)", r.md.ID())
|
||||
}
|
||||
|
||||
// DeepCopy implements resource.Resource.
|
||||
func (r *ExtensionStatus) DeepCopy() resource.Resource {
|
||||
return &ExtensionStatus{
|
||||
md: r.md,
|
||||
spec: r.spec,
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceDefinition implements meta.ResourceDefinitionProvider interface.
|
||||
func (r *ExtensionStatus) ResourceDefinition() meta.ResourceDefinitionSpec {
|
||||
return meta.ResourceDefinitionSpec{
|
||||
Type: ExtensionStatusType,
|
||||
Aliases: []resource.Type{"extensions"},
|
||||
DefaultNamespace: NamespaceName,
|
||||
PrintColumns: []meta.PrintColumn{
|
||||
{
|
||||
Name: "Name",
|
||||
JSONPath: `{.metadata.name}`,
|
||||
},
|
||||
{
|
||||
Name: "Version",
|
||||
JSONPath: `{.metadata.version}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TypedSpec allows to access the ExtensionStatusSpec with the proper type.
|
||||
func (r *ExtensionStatus) TypedSpec() *ExtensionStatusSpec {
|
||||
return &r.spec
|
||||
}
|
@ -25,6 +25,7 @@ func TestRegisterResource(t *testing.T) {
|
||||
resourceRegistry := registry.NewResourceRegistry(resources)
|
||||
|
||||
for _, resource := range []resource.Resource{
|
||||
&runtime.ExtensionStatus{},
|
||||
&runtime.KernelModuleSpec{},
|
||||
&runtime.KernelParamSpec{},
|
||||
&runtime.KernelParamStatus{},
|
||||
|
Loading…
x
Reference in New Issue
Block a user