mirror of
https://github.com/siderolabs/omni.git
synced 2026-05-04 22:26:13 +02:00
feat: download talosctl directly from factory
Download talosctl binaries from factory instead of Github Signed-off-by: Edward Sammut Alessi <edward.sammutalessi@siderolabs.com>
This commit is contained in:
parent
b2671d08d0
commit
d3592671ec
@ -927,6 +927,58 @@ func (x *SupportSpec) GetOfficeHours() *OfficeHoursConfig {
|
||||
return nil
|
||||
}
|
||||
|
||||
type QuirksSpec struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
SupportsUnifiedInstaller bool `protobuf:"varint,1,opt,name=supports_unified_installer,json=supportsUnifiedInstaller,proto3" json:"supports_unified_installer,omitempty"`
|
||||
SupportsFactoryTalosctl bool `protobuf:"varint,2,opt,name=supports_factory_talosctl,json=supportsFactoryTalosctl,proto3" json:"supports_factory_talosctl,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *QuirksSpec) Reset() {
|
||||
*x = QuirksSpec{}
|
||||
mi := &file_omni_specs_virtual_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *QuirksSpec) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*QuirksSpec) ProtoMessage() {}
|
||||
|
||||
func (x *QuirksSpec) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_omni_specs_virtual_proto_msgTypes[9]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use QuirksSpec.ProtoReflect.Descriptor instead.
|
||||
func (*QuirksSpec) Descriptor() ([]byte, []int) {
|
||||
return file_omni_specs_virtual_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *QuirksSpec) GetSupportsUnifiedInstaller() bool {
|
||||
if x != nil {
|
||||
return x.SupportsUnifiedInstaller
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *QuirksSpec) GetSupportsFactoryTalosctl() bool {
|
||||
if x != nil {
|
||||
return x.SupportsFactoryTalosctl
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type LabelsCompletionSpec_Values struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Items []string `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
|
||||
@ -936,7 +988,7 @@ type LabelsCompletionSpec_Values struct {
|
||||
|
||||
func (x *LabelsCompletionSpec_Values) Reset() {
|
||||
*x = LabelsCompletionSpec_Values{}
|
||||
mi := &file_omni_specs_virtual_proto_msgTypes[9]
|
||||
mi := &file_omni_specs_virtual_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -948,7 +1000,7 @@ func (x *LabelsCompletionSpec_Values) String() string {
|
||||
func (*LabelsCompletionSpec_Values) ProtoMessage() {}
|
||||
|
||||
func (x *LabelsCompletionSpec_Values) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_omni_specs_virtual_proto_msgTypes[9]
|
||||
mi := &file_omni_specs_virtual_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1068,7 +1120,11 @@ const file_omni_specs_virtual_proto_rawDesc = "" +
|
||||
"\bmeet_url\x18\b \x01(\tR\ameetUrl\"s\n" +
|
||||
"\vSupportSpec\x12'\n" +
|
||||
"\x0fsupport_enabled\x18\x01 \x01(\bR\x0esupportEnabled\x12;\n" +
|
||||
"\foffice_hours\x18\x02 \x01(\v2\x18.specs.OfficeHoursConfigR\vofficeHoursB2Z0github.com/siderolabs/omni/client/api/omni/specsb\x06proto3"
|
||||
"\foffice_hours\x18\x02 \x01(\v2\x18.specs.OfficeHoursConfigR\vofficeHours\"\x86\x01\n" +
|
||||
"\n" +
|
||||
"QuirksSpec\x12<\n" +
|
||||
"\x1asupports_unified_installer\x18\x01 \x01(\bR\x18supportsUnifiedInstaller\x12:\n" +
|
||||
"\x19supports_factory_talosctl\x18\x02 \x01(\bR\x17supportsFactoryTalosctlB2Z0github.com/siderolabs/omni/client/api/omni/specsb\x06proto3"
|
||||
|
||||
var (
|
||||
file_omni_specs_virtual_proto_rawDescOnce sync.Once
|
||||
@ -1083,7 +1139,7 @@ func file_omni_specs_virtual_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_omni_specs_virtual_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_omni_specs_virtual_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||
var file_omni_specs_virtual_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
|
||||
var file_omni_specs_virtual_proto_goTypes = []any{
|
||||
(PlatformConfigSpec_BootMethod)(0), // 0: specs.PlatformConfigSpec.BootMethod
|
||||
(PlatformConfigSpec_Arch)(0), // 1: specs.PlatformConfigSpec.Arch
|
||||
@ -1096,15 +1152,16 @@ var file_omni_specs_virtual_proto_goTypes = []any{
|
||||
(*SBCConfigSpec)(nil), // 8: specs.SBCConfigSpec
|
||||
(*OfficeHoursConfig)(nil), // 9: specs.OfficeHoursConfig
|
||||
(*SupportSpec)(nil), // 10: specs.SupportSpec
|
||||
(*LabelsCompletionSpec_Values)(nil), // 11: specs.LabelsCompletionSpec.Values
|
||||
nil, // 12: specs.LabelsCompletionSpec.ItemsEntry
|
||||
(*QuirksSpec)(nil), // 11: specs.QuirksSpec
|
||||
(*LabelsCompletionSpec_Values)(nil), // 12: specs.LabelsCompletionSpec.Values
|
||||
nil, // 13: specs.LabelsCompletionSpec.ItemsEntry
|
||||
}
|
||||
var file_omni_specs_virtual_proto_depIdxs = []int32{
|
||||
12, // 0: specs.LabelsCompletionSpec.items:type_name -> specs.LabelsCompletionSpec.ItemsEntry
|
||||
13, // 0: specs.LabelsCompletionSpec.items:type_name -> specs.LabelsCompletionSpec.ItemsEntry
|
||||
1, // 1: specs.PlatformConfigSpec.architectures:type_name -> specs.PlatformConfigSpec.Arch
|
||||
0, // 2: specs.PlatformConfigSpec.boot_methods:type_name -> specs.PlatformConfigSpec.BootMethod
|
||||
9, // 3: specs.SupportSpec.office_hours:type_name -> specs.OfficeHoursConfig
|
||||
11, // 4: specs.LabelsCompletionSpec.ItemsEntry.value:type_name -> specs.LabelsCompletionSpec.Values
|
||||
12, // 4: specs.LabelsCompletionSpec.ItemsEntry.value:type_name -> specs.LabelsCompletionSpec.Values
|
||||
5, // [5:5] is the sub-list for method output_type
|
||||
5, // [5:5] is the sub-list for method input_type
|
||||
5, // [5:5] is the sub-list for extension type_name
|
||||
@ -1123,7 +1180,7 @@ func file_omni_specs_virtual_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_omni_specs_virtual_proto_rawDesc), len(file_omni_specs_virtual_proto_rawDesc)),
|
||||
NumEnums: 2,
|
||||
NumMessages: 11,
|
||||
NumMessages: 12,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@ -107,3 +107,8 @@ message SupportSpec {
|
||||
bool support_enabled = 1;
|
||||
OfficeHoursConfig office_hours = 2;
|
||||
}
|
||||
|
||||
message QuirksSpec {
|
||||
bool supports_unified_installer = 1;
|
||||
bool supports_factory_talosctl = 2;
|
||||
}
|
||||
|
||||
@ -259,6 +259,24 @@ func (m *SupportSpec) CloneMessageVT() proto.Message {
|
||||
return m.CloneVT()
|
||||
}
|
||||
|
||||
func (m *QuirksSpec) CloneVT() *QuirksSpec {
|
||||
if m == nil {
|
||||
return (*QuirksSpec)(nil)
|
||||
}
|
||||
r := new(QuirksSpec)
|
||||
r.SupportsUnifiedInstaller = m.SupportsUnifiedInstaller
|
||||
r.SupportsFactoryTalosctl = m.SupportsFactoryTalosctl
|
||||
if len(m.unknownFields) > 0 {
|
||||
r.unknownFields = make([]byte, len(m.unknownFields))
|
||||
copy(r.unknownFields, m.unknownFields)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (m *QuirksSpec) CloneMessageVT() proto.Message {
|
||||
return m.CloneVT()
|
||||
}
|
||||
|
||||
func (this *CurrentUserSpec) EqualVT(that *CurrentUserSpec) bool {
|
||||
if this == that {
|
||||
return true
|
||||
@ -637,6 +655,28 @@ func (this *SupportSpec) EqualMessageVT(thatMsg proto.Message) bool {
|
||||
}
|
||||
return this.EqualVT(that)
|
||||
}
|
||||
func (this *QuirksSpec) EqualVT(that *QuirksSpec) bool {
|
||||
if this == that {
|
||||
return true
|
||||
} else if this == nil || that == nil {
|
||||
return false
|
||||
}
|
||||
if this.SupportsUnifiedInstaller != that.SupportsUnifiedInstaller {
|
||||
return false
|
||||
}
|
||||
if this.SupportsFactoryTalosctl != that.SupportsFactoryTalosctl {
|
||||
return false
|
||||
}
|
||||
return string(this.unknownFields) == string(that.unknownFields)
|
||||
}
|
||||
|
||||
func (this *QuirksSpec) EqualMessageVT(thatMsg proto.Message) bool {
|
||||
that, ok := thatMsg.(*QuirksSpec)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return this.EqualVT(that)
|
||||
}
|
||||
func (m *CurrentUserSpec) MarshalVT() (dAtA []byte, err error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
@ -1552,6 +1592,59 @@ func (m *SupportSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *QuirksSpec) MarshalVT() (dAtA []byte, err error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
size := m.SizeVT()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *QuirksSpec) MarshalToVT(dAtA []byte) (int, error) {
|
||||
size := m.SizeVT()
|
||||
return m.MarshalToSizedBufferVT(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *QuirksSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
|
||||
if m == nil {
|
||||
return 0, nil
|
||||
}
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.unknownFields != nil {
|
||||
i -= len(m.unknownFields)
|
||||
copy(dAtA[i:], m.unknownFields)
|
||||
}
|
||||
if m.SupportsFactoryTalosctl {
|
||||
i--
|
||||
if m.SupportsFactoryTalosctl {
|
||||
dAtA[i] = 1
|
||||
} else {
|
||||
dAtA[i] = 0
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
if m.SupportsUnifiedInstaller {
|
||||
i--
|
||||
if m.SupportsUnifiedInstaller {
|
||||
dAtA[i] = 1
|
||||
} else {
|
||||
dAtA[i] = 0
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x8
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *CurrentUserSpec) SizeVT() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
@ -1879,6 +1972,22 @@ func (m *SupportSpec) SizeVT() (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *QuirksSpec) SizeVT() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.SupportsUnifiedInstaller {
|
||||
n += 2
|
||||
}
|
||||
if m.SupportsFactoryTalosctl {
|
||||
n += 2
|
||||
}
|
||||
n += len(m.unknownFields)
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *CurrentUserSpec) UnmarshalVT(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
@ -4108,3 +4217,94 @@ func (m *SupportSpec) UnmarshalVT(dAtA []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *QuirksSpec) UnmarshalVT(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: QuirksSpec: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: QuirksSpec: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field SupportsUnifiedInstaller", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
v |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.SupportsUnifiedInstaller = bool(v != 0)
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field SupportsFactoryTalosctl", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
v |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.SupportsFactoryTalosctl = bool(v != 0)
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
49
client/pkg/omni/resources/virtual/quirk.go
Normal file
49
client/pkg/omni/resources/virtual/quirk.go
Normal file
@ -0,0 +1,49 @@
|
||||
// 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 virtual
|
||||
|
||||
import (
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/resource/meta"
|
||||
"github.com/cosi-project/runtime/pkg/resource/protobuf"
|
||||
"github.com/cosi-project/runtime/pkg/resource/typed"
|
||||
|
||||
"github.com/siderolabs/omni/client/api/omni/specs"
|
||||
"github.com/siderolabs/omni/client/pkg/omni/resources"
|
||||
)
|
||||
|
||||
// NewQuirks creates a new Quirks resource.
|
||||
func NewQuirks(id string) *Quirks {
|
||||
return typed.NewResource[QuirksSpec, QuirksExtension](
|
||||
resource.NewMetadata(resources.VirtualNamespace, QuirksType, id, resource.VersionUndefined),
|
||||
protobuf.NewResourceSpec(&specs.QuirksSpec{}),
|
||||
)
|
||||
}
|
||||
|
||||
const (
|
||||
// QuirksType is the type of Quirks resource.
|
||||
//
|
||||
// tsgen:QuirksType
|
||||
QuirksType = resource.Type("Quirks.omni.sidero.dev")
|
||||
)
|
||||
|
||||
// Quirks resource describes the current Stripe subscription plan.
|
||||
type Quirks = typed.Resource[QuirksSpec, QuirksExtension]
|
||||
|
||||
// QuirksSpec wraps specs.QuirksSpec.
|
||||
type QuirksSpec = protobuf.ResourceSpec[specs.QuirksSpec, *specs.QuirksSpec]
|
||||
|
||||
// QuirksExtension provides auxiliary methods for Quirks resource.
|
||||
type QuirksExtension struct{}
|
||||
|
||||
// ResourceDefinition implements [typed.Extension] interface.
|
||||
func (QuirksExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
|
||||
return meta.ResourceDefinitionSpec{
|
||||
Type: QuirksType,
|
||||
Aliases: []resource.Type{},
|
||||
DefaultNamespace: resources.VirtualNamespace,
|
||||
PrintColumns: []meta.PrintColumn{},
|
||||
}
|
||||
}
|
||||
@ -18,4 +18,5 @@ func init() {
|
||||
registry.MustRegisterResource(CloudPlatformConfigType, &CloudPlatformConfig{})
|
||||
registry.MustRegisterResource(MetalPlatformConfigType, &MetalPlatformConfig{})
|
||||
registry.MustRegisterResource(SupportType, &Support{})
|
||||
registry.MustRegisterResource(QuirksType, &Quirks{})
|
||||
}
|
||||
|
||||
@ -132,6 +132,26 @@ test('Download omniconfig', async ({ page }, testInfo) => {
|
||||
})
|
||||
})
|
||||
|
||||
test('Download talosctl', async ({ page }, testInfo) => {
|
||||
await page.goto('/')
|
||||
|
||||
await page.getByRole('button', { name: 'Download talosctl' }).click()
|
||||
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent('download'),
|
||||
page.getByRole('link', { name: 'Download', exact: true }).click(),
|
||||
])
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Home' })).toBeVisible()
|
||||
|
||||
const filePath = testInfo.outputPath(download.suggestedFilename())
|
||||
await download.saveAs(filePath)
|
||||
|
||||
const { size } = await stat(filePath)
|
||||
|
||||
expect(size).toBeGreaterThan(5 * 1024 * 1024)
|
||||
})
|
||||
|
||||
test('Download omnictl', async ({ page }, testInfo) => {
|
||||
await page.goto('/')
|
||||
|
||||
|
||||
@ -106,4 +106,9 @@ export type OfficeHoursConfig = {
|
||||
export type SupportSpec = {
|
||||
support_enabled?: boolean
|
||||
office_hours?: OfficeHoursConfig
|
||||
}
|
||||
|
||||
export type QuirksSpec = {
|
||||
supports_unified_installer?: boolean
|
||||
supports_factory_talosctl?: boolean
|
||||
}
|
||||
@ -287,6 +287,7 @@ export const LabelsCompletionType = "LabelsCompletions.omni.sidero.dev";
|
||||
export const MetalPlatformConfigType = "MetalPlatformConfigs.omni.sidero.dev";
|
||||
export const PermissionsID = "permissions";
|
||||
export const PermissionsType = "Permissions.omni.sidero.dev";
|
||||
export const QuirksType = "Quirks.omni.sidero.dev";
|
||||
export const SBCConfigType = "SBCConfigs.omni.sidero.dev";
|
||||
export const SupportID = "support";
|
||||
export const SupportType = "Supports.omni.sidero.dev";
|
||||
|
||||
@ -31,6 +31,7 @@ const props = withDefaults(
|
||||
actionLabel?: string
|
||||
cancelLabel?: string
|
||||
actionDisabled?: boolean
|
||||
actionHref?: string
|
||||
loading?: boolean
|
||||
}
|
||||
>(),
|
||||
@ -45,6 +46,7 @@ const dialogRootProps = reactiveOmit(
|
||||
'actionLabel',
|
||||
'cancelLabel',
|
||||
'actionDisabled',
|
||||
'actionHref',
|
||||
'loading',
|
||||
)
|
||||
const forwarded = useForwardPropsEmits(dialogRootProps, emit)
|
||||
@ -58,7 +60,7 @@ const forwarded = useForwardPropsEmits(dialogRootProps, emit)
|
||||
/>
|
||||
|
||||
<DialogContent
|
||||
class="fixed top-1/2 left-1/2 z-100 flex max-h-screen max-w-screen -translate-1/2 flex-col rounded-sm bg-naturals-n3 p-8 zoom-in-75 zoom-out-75 fade-in fade-out data-[state=closed]:animate-out data-[state=open]:animate-in"
|
||||
class="fixed top-1/2 left-1/2 z-30 flex max-h-screen max-w-screen -translate-1/2 flex-col rounded-sm bg-naturals-n3 p-8 zoom-in-75 zoom-out-75 fade-in fade-out data-[state=closed]:animate-out data-[state=open]:animate-in"
|
||||
>
|
||||
<div class="mb-5 flex items-start justify-between gap-4">
|
||||
<div class="flex flex-col">
|
||||
@ -89,6 +91,7 @@ const forwarded = useForwardPropsEmits(dialogRootProps, emit)
|
||||
v-if="actionLabel"
|
||||
:disabled="actionDisabled || loading"
|
||||
variant="highlighted"
|
||||
v-bind="actionHref ? { is: 'a', href: actionHref } : { is: 'button' }"
|
||||
@click="$emit('confirm')"
|
||||
>
|
||||
<TSpinner v-if="loading" class="size-5" />
|
||||
|
||||
@ -3,52 +3,47 @@
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
import { computedAsync } from '@vueuse/core'
|
||||
import { compareLoose, gte } from 'semver'
|
||||
import { computed } from 'vue'
|
||||
import { type MaybeRefOrGetter, ref, toValue } from 'vue'
|
||||
|
||||
import { MinTalosVersion } from '@/api/resources'
|
||||
import { showError } from '@/notification'
|
||||
import { DefaultTalosVersion } from '@/api/resources'
|
||||
|
||||
export interface TalosctlDownloadsResponse {
|
||||
status: string
|
||||
release_data: {
|
||||
/**
|
||||
* NOTE: We don't use this response value as it is not correct.
|
||||
* The backend pops the top of a sorted list, but it is sorted alphabetically,
|
||||
* which does not correctly sort versions. For example, this sorting:
|
||||
* - 1.1
|
||||
* - 1.11
|
||||
* - 1.2
|
||||
*
|
||||
* Giving 1.2 as a more recent version than 1.11.
|
||||
*
|
||||
* @deprecated Don't use this as it is not the latest version.
|
||||
*/
|
||||
default_version: string
|
||||
available_versions: Record<string, { name: string; url: string }[]>
|
||||
}
|
||||
downloads?: string[]
|
||||
}
|
||||
|
||||
export function useTalosctlDownloads() {
|
||||
const downloads = computedAsync(async () => {
|
||||
try {
|
||||
const response = await fetch('/talosctl/downloads')
|
||||
|
||||
const {
|
||||
release_data: { available_versions },
|
||||
}: TalosctlDownloadsResponse = await response.json()
|
||||
|
||||
return new Map(
|
||||
Object.entries(available_versions)
|
||||
.filter(([v]) => gte(v, MinTalosVersion))
|
||||
.sort(([a], [b]) => compareLoose(a, b)),
|
||||
)
|
||||
} catch (e) {
|
||||
showError('Error getting latest talos releases', e?.message ?? String(e))
|
||||
}
|
||||
})
|
||||
|
||||
const defaultVersion = computed(() => downloads.value && Array.from(downloads.value.keys()).pop())
|
||||
|
||||
return { downloads, defaultVersion }
|
||||
interface Options {
|
||||
skip?: boolean
|
||||
}
|
||||
|
||||
export function useTalosctlDownloads(
|
||||
talosVersionMaybeRef?: MaybeRefOrGetter<string | undefined>,
|
||||
options?: MaybeRefOrGetter<Options>,
|
||||
) {
|
||||
const loading = ref(false)
|
||||
const err = ref<Error>()
|
||||
|
||||
const data = computedAsync(
|
||||
async () => {
|
||||
try {
|
||||
if (toValue(options)?.skip) return []
|
||||
|
||||
const talosVersion = toValue(talosVersionMaybeRef) ?? DefaultTalosVersion
|
||||
|
||||
const response = await fetch(`/talosctl/downloads/${talosVersion}`)
|
||||
|
||||
const { downloads }: TalosctlDownloadsResponse = await response.json()
|
||||
|
||||
return downloads ?? []
|
||||
} catch (e) {
|
||||
err.value = e instanceof Error ? e : new Error(String(e))
|
||||
|
||||
return []
|
||||
}
|
||||
},
|
||||
[],
|
||||
loading,
|
||||
)
|
||||
|
||||
return { data, loading, err }
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
type PlatformConfigSpec,
|
||||
PlatformConfigSpecArch,
|
||||
PlatformConfigSpecBootMethod,
|
||||
type QuirksSpec,
|
||||
type SBCConfigSpec,
|
||||
} from '@/api/omni/specs/virtual.pb'
|
||||
import {
|
||||
@ -29,6 +30,7 @@ import {
|
||||
LabelsMeta,
|
||||
MetalPlatformConfigType,
|
||||
PlatformMetalID,
|
||||
QuirksType,
|
||||
SBCConfigType,
|
||||
VirtualNamespace,
|
||||
} from '@/api/resources'
|
||||
@ -83,26 +85,40 @@ export const Default = {
|
||||
],
|
||||
}).handler,
|
||||
|
||||
http.get('/talosctl/downloads', () => {
|
||||
const versions = Object.fromEntries(
|
||||
faker.helpers.multiple(faker.system.semver).map(
|
||||
(v) =>
|
||||
[
|
||||
`v${v}`,
|
||||
faker.helpers.multiple(faker.hacker.noun, { count: 5 }).map((name) => ({
|
||||
name,
|
||||
url: `https://github.com/siderolabs/talos/releases/download/v${v}/talosctl-${name}-${faker.helpers.arrayElement(['amd64', 'arm64'])}`,
|
||||
})),
|
||||
] as const,
|
||||
),
|
||||
)
|
||||
http.post<never, GetRequest, GetResponse>(
|
||||
'/omni.resources.ResourceService/Get',
|
||||
async ({ request }) => {
|
||||
const { id, type, namespace } = await request.clone().json()
|
||||
|
||||
if (type !== QuirksType || namespace !== VirtualNamespace) return
|
||||
|
||||
return HttpResponse.json({
|
||||
body: JSON.stringify({
|
||||
metadata: {
|
||||
namespace,
|
||||
type,
|
||||
id,
|
||||
},
|
||||
spec: {
|
||||
supports_unified_installer: true,
|
||||
supports_factory_talosctl: true,
|
||||
},
|
||||
} satisfies Resource<QuirksSpec>),
|
||||
})
|
||||
},
|
||||
),
|
||||
|
||||
http.get<{ version: string }>('/talosctl/downloads/:version', ({ params: { version } }) => {
|
||||
const downloads = faker.helpers
|
||||
.multiple(faker.hacker.noun, { count: 5 })
|
||||
.map(
|
||||
(name) =>
|
||||
`https://factory.talos.dev/talosctl/v${version}/talosctl-${name}-${faker.helpers.arrayElement(['amd64', 'arm64'])}`,
|
||||
)
|
||||
|
||||
return HttpResponse.json<TalosctlDownloadsResponse>({
|
||||
status: '',
|
||||
release_data: {
|
||||
default_version: '',
|
||||
available_versions: versions,
|
||||
},
|
||||
downloads,
|
||||
})
|
||||
}),
|
||||
|
||||
|
||||
@ -3,68 +3,97 @@
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the LICENSE file.
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { createWatchStreamHandler } from '@msw/helpers'
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
import { http, HttpResponse } from 'msw'
|
||||
import { compare } from 'semver'
|
||||
|
||||
import type { Resource } from '@/api/grpc'
|
||||
import type { ListRequest, ListResponse } from '@/api/omni/resources/resources.pb'
|
||||
import type { TalosVersionSpec } from '@/api/omni/specs/omni.pb'
|
||||
import type { QuirksSpec } from '@/api/omni/specs/virtual.pb'
|
||||
import {
|
||||
DefaultNamespace,
|
||||
DefaultTalosVersion,
|
||||
QuirksType,
|
||||
TalosVersionType,
|
||||
VirtualNamespace,
|
||||
} from '@/api/resources'
|
||||
import type { TalosctlDownloadsResponse } from '@/methods/useTalosctlDownloads'
|
||||
|
||||
import DownloadTalosctl from './DownloadTalosctl.vue'
|
||||
|
||||
const meta: Meta<typeof DownloadTalosctl> = {
|
||||
component: DownloadTalosctl,
|
||||
args: {
|
||||
open: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const versions = faker.helpers
|
||||
.uniqueArray(faker.system.semver, 10)
|
||||
.uniqueArray<string>(
|
||||
() => `1.${faker.number.int({ min: 8, max: 13 })}.${faker.number.int({ min: 0, max: 10 })}`,
|
||||
40,
|
||||
)
|
||||
.concat(DefaultTalosVersion)
|
||||
.sort(compare)
|
||||
.map((v) => `v${v}`)
|
||||
|
||||
export const Default: Story = {
|
||||
parameters: {
|
||||
msw: {
|
||||
handlers: [
|
||||
http.get('/talosctl/downloads', () =>
|
||||
HttpResponse.json({
|
||||
release_data: {
|
||||
available_versions: versions.reduce<Record<string, { name: string; url: string }[]>>(
|
||||
(prev, curr) => ({
|
||||
...prev,
|
||||
[curr]: [
|
||||
{
|
||||
name: 'Apple',
|
||||
url: `https://github.com/siderolabs/talos/releases/download/${curr}/talosctl-darwin-amd64`,
|
||||
},
|
||||
{
|
||||
name: 'Apple Silicon',
|
||||
url: `https://github.com/siderolabs/talos/releases/download/${curr}/talosctl-darwin-arm64`,
|
||||
},
|
||||
{
|
||||
name: 'Linux',
|
||||
url: `https://github.com/siderolabs/talos/releases/download/${curr}/talosctl-linux-amd64`,
|
||||
},
|
||||
{
|
||||
name: 'Linux ARM',
|
||||
url: `https://github.com/siderolabs/talos/releases/download/${curr}/talosctl-linux-armv7`,
|
||||
},
|
||||
{
|
||||
name: 'Linux ARM64',
|
||||
url: `https://github.com/siderolabs/talos/releases/download/${curr}/talosctl-linux-arm64`,
|
||||
},
|
||||
{
|
||||
name: 'Windows',
|
||||
url: `https://github.com/siderolabs/talos/releases/download/${curr}/talosctl-windows-amd64.exe`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
{},
|
||||
http.post<never, ListRequest, ListResponse>(
|
||||
'/omni.resources.ResourceService/List',
|
||||
async ({ request }) => {
|
||||
const { type, namespace } = await request.clone().json()
|
||||
|
||||
if (type !== QuirksType || namespace !== VirtualNamespace) return
|
||||
|
||||
return HttpResponse.json({
|
||||
total: versions.length,
|
||||
items: versions.map((version) =>
|
||||
JSON.stringify({
|
||||
metadata: {
|
||||
namespace,
|
||||
type,
|
||||
id: version,
|
||||
},
|
||||
spec: {
|
||||
supports_factory_talosctl: faker.datatype.boolean(),
|
||||
},
|
||||
} satisfies Resource<QuirksSpec>),
|
||||
),
|
||||
default_version: versions.at(-1),
|
||||
},
|
||||
status: 'ok',
|
||||
}),
|
||||
})
|
||||
},
|
||||
),
|
||||
|
||||
createWatchStreamHandler<TalosVersionSpec>({
|
||||
expectedOptions: {
|
||||
type: TalosVersionType,
|
||||
namespace: DefaultNamespace,
|
||||
},
|
||||
initialResources: versions.map((version) => ({
|
||||
spec: { version, deprecated: faker.datatype.boolean() },
|
||||
metadata: { id: version },
|
||||
})),
|
||||
}).handler,
|
||||
|
||||
http.get<{ version: string }>('/talosctl/downloads/:version', ({ params: { version } }) => {
|
||||
const downloads = faker.helpers
|
||||
.multiple(faker.hacker.noun, { count: 5 })
|
||||
.map(
|
||||
(name) =>
|
||||
`https://factory.talos.dev/talosctl/v${version}/talosctl-${name}-${faker.helpers.arrayElement(['amd64', 'arm64'])}`,
|
||||
)
|
||||
|
||||
return HttpResponse.json<TalosctlDownloadsResponse>({
|
||||
status: '',
|
||||
downloads,
|
||||
})
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@ -6,60 +6,124 @@ included in the LICENSE file.
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import { computedAsync } from '@vueuse/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import { compare } from 'semver'
|
||||
import { computed, ref, toValue, watchEffect } from 'vue'
|
||||
|
||||
import { Runtime } from '@/api/common/omni.pb'
|
||||
import type { TalosVersionSpec } from '@/api/omni/specs/omni.pb'
|
||||
import type { QuirksSpec } from '@/api/omni/specs/virtual.pb'
|
||||
import {
|
||||
DefaultNamespace,
|
||||
DefaultTalosVersion,
|
||||
QuirksType,
|
||||
TalosVersionType,
|
||||
VirtualNamespace,
|
||||
} from '@/api/resources'
|
||||
import CodeBlock from '@/components/CodeBlock/CodeBlock.vue'
|
||||
import Modal from '@/components/Modals/Modal.vue'
|
||||
import TSelectList from '@/components/SelectList/TSelectList.vue'
|
||||
import TSpinner from '@/components/Spinner/TSpinner.vue'
|
||||
import { downloadFile, getDocsLink, getPlatform } from '@/methods'
|
||||
import TAlert from '@/components/TAlert.vue'
|
||||
import { getDocsLink, getPlatform } from '@/methods'
|
||||
import { useResourceList } from '@/methods/useResourceList'
|
||||
import { useResourceWatch } from '@/methods/useResourceWatch'
|
||||
import { useTalosctlDownloads } from '@/methods/useTalosctlDownloads'
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
|
||||
const platform = computedAsync(getPlatform)
|
||||
|
||||
const { downloads: availableVersions, defaultVersion } = useTalosctlDownloads()
|
||||
const selectedVersion = ref<string>()
|
||||
const selectedBinary = ref<string>()
|
||||
|
||||
const defaultPlatform = computed(() => {
|
||||
if (!defaultVersion.value || !platform.value) return
|
||||
const { data: quirks } = useResourceList<QuirksSpec>(() => ({
|
||||
skip: !open.value,
|
||||
runtime: Runtime.Omni,
|
||||
resource: {
|
||||
type: QuirksType,
|
||||
namespace: VirtualNamespace,
|
||||
},
|
||||
}))
|
||||
|
||||
const {
|
||||
data: versions,
|
||||
loading: versionsLoading,
|
||||
err: versionsErr,
|
||||
} = useResourceWatch<TalosVersionSpec>(() => ({
|
||||
skip: !open.value,
|
||||
runtime: Runtime.Omni,
|
||||
resource: {
|
||||
type: TalosVersionType,
|
||||
namespace: DefaultNamespace,
|
||||
},
|
||||
}))
|
||||
|
||||
const {
|
||||
data: binaries,
|
||||
loading: binariesLoading,
|
||||
err: binariesErr,
|
||||
} = useTalosctlDownloads(selectedVersion, () => ({ skip: !open.value }))
|
||||
|
||||
function getBinaryNameFromURL(url: string) {
|
||||
return new URL(url).pathname.split('/').pop()
|
||||
}
|
||||
|
||||
const versionsList = computed(() =>
|
||||
versions.value
|
||||
.filter(
|
||||
(v) =>
|
||||
!v.spec.deprecated &&
|
||||
quirks.value?.find((q) => q.metadata.id === v.spec.version)?.spec.supports_factory_talosctl,
|
||||
)
|
||||
.map((v) => v.spec.version!)
|
||||
.sort(compare),
|
||||
)
|
||||
|
||||
const binariesList = computed(() =>
|
||||
binaries.value.map((b) => ({
|
||||
label: getBinaryNameFromURL(b) ?? b,
|
||||
value: b,
|
||||
})),
|
||||
)
|
||||
|
||||
watchEffect(() => {
|
||||
if (!open.value) {
|
||||
selectedVersion.value = undefined
|
||||
selectedBinary.value = undefined
|
||||
}
|
||||
|
||||
const selected = toValue(selectedBinary.value)
|
||||
|
||||
if (selected && !binariesList.value.some((b) => b.value === selected)) {
|
||||
selectedBinary.value = binariesList.value.find(
|
||||
(b) => getBinaryNameFromURL(b.value) === getBinaryNameFromURL(selected),
|
||||
)?.value
|
||||
}
|
||||
})
|
||||
|
||||
const defaultBinary = computed(() => {
|
||||
if (!binaries.value || !platform.value) return
|
||||
|
||||
const [os, arch] = platform.value
|
||||
|
||||
const assets = availableVersions.value?.get(defaultVersion.value)
|
||||
const defaultAsset = assets?.find((item) => item.url.endsWith('linux-amd64'))
|
||||
const preferredAsset = assets?.find((item) => item.url.endsWith(`${os}-${arch}`))
|
||||
const ext = os === 'windows' ? '.exe' : ''
|
||||
|
||||
return (preferredAsset ?? defaultAsset)?.name
|
||||
})
|
||||
const defaultAsset = binaries.value.find((item) => item.endsWith('linux-amd64'))
|
||||
const preferredAsset = binaries.value.find((item) => item.endsWith(`${os}-${arch}${ext}`))
|
||||
|
||||
const selectedVersion = ref<string>()
|
||||
const selectedPlatform = ref<string>()
|
||||
|
||||
const download = () => {
|
||||
open.value = false
|
||||
|
||||
if (!selectedVersion.value) return
|
||||
|
||||
const link = availableVersions.value
|
||||
?.get(selectedVersion.value)
|
||||
?.find((item) => item.name === selectedPlatform.value)
|
||||
if (!link) {
|
||||
return
|
||||
}
|
||||
|
||||
downloadFile(link.url)
|
||||
}
|
||||
|
||||
const versionBinaries = computed<string[]>(() => {
|
||||
if (!selectedVersion.value) return []
|
||||
|
||||
return availableVersions.value?.get(selectedVersion.value)?.map((item) => item.name) ?? []
|
||||
return preferredAsset ?? defaultAsset
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model:open="open" title="Download Talosctl" action-label="Download" @confirm="download">
|
||||
<Modal
|
||||
v-model:open="open"
|
||||
title="Download Talosctl"
|
||||
action-label="Download"
|
||||
:action-disabled="!selectedBinary"
|
||||
:action-href="selectedBinary ?? ''"
|
||||
:loading="binariesLoading"
|
||||
@confirm="open = false"
|
||||
>
|
||||
<template #description>
|
||||
<code>talosctl</code>
|
||||
can be used to access cluster nodes using Talos machine API. Read the
|
||||
@ -81,26 +145,35 @@ const versionBinaries = computed<string[]>(() => {
|
||||
|
||||
<span class="mb-2 text-xs text-naturals-n14">Manual installation</span>
|
||||
|
||||
<div class="mb-5 flex flex-wrap gap-4">
|
||||
<div v-if="availableVersions && platform" class="flex flex-wrap gap-4">
|
||||
<TSelectList
|
||||
v-model="selectedVersion"
|
||||
title="version"
|
||||
:default-value="defaultVersion"
|
||||
:values="Array.from(availableVersions.keys())"
|
||||
searcheable
|
||||
/>
|
||||
<TSelectList
|
||||
v-model="selectedPlatform"
|
||||
title="talosctl"
|
||||
:default-value="defaultPlatform"
|
||||
:values="versionBinaries"
|
||||
searcheable
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<TSpinner class="h-6 w-6" />
|
||||
</div>
|
||||
<TAlert v-if="versionsErr || binariesErr" title="Failed to get talosctl versions" type="error">
|
||||
{{ versionsErr }}
|
||||
{{ binariesErr }}
|
||||
</TAlert>
|
||||
|
||||
<TAlert
|
||||
v-else-if="!binariesLoading && !binariesList.length"
|
||||
type="warn"
|
||||
title="No binaries found"
|
||||
>
|
||||
No talosctl binaries were found for version {{ selectedVersion }}
|
||||
</TAlert>
|
||||
|
||||
<div class="mt-2 mb-5 flex flex-wrap gap-4">
|
||||
<TSelectList
|
||||
v-if="!versionsLoading && !versionsErr"
|
||||
v-model="selectedVersion"
|
||||
title="Talos version"
|
||||
:default-value="DefaultTalosVersion"
|
||||
:values="versionsList"
|
||||
/>
|
||||
|
||||
<TSelectList
|
||||
v-if="binariesList.length"
|
||||
v-model="selectedBinary"
|
||||
title="Platform"
|
||||
:default-value="defaultBinary"
|
||||
:values="binariesList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="flex text-xs">
|
||||
|
||||
@ -13,12 +13,14 @@ import { Runtime } from '@/api/common/omni.pb'
|
||||
import {
|
||||
type PlatformConfigSpec,
|
||||
PlatformConfigSpecBootMethod,
|
||||
type QuirksSpec,
|
||||
type SBCConfigSpec,
|
||||
} from '@/api/omni/specs/virtual.pb'
|
||||
import {
|
||||
CloudPlatformConfigType,
|
||||
MetalPlatformConfigType,
|
||||
PlatformMetalID,
|
||||
QuirksType,
|
||||
SBCConfigType,
|
||||
VirtualNamespace,
|
||||
} from '@/api/resources'
|
||||
@ -48,8 +50,17 @@ const formState = defineModel<FormState>({ required: true })
|
||||
|
||||
const resolvedTalosVersion = computed(() => resolveTalosVersion(formState.value.talosVersion!))
|
||||
|
||||
const supportsUnifiedInstaller = computed(() => gte(resolvedTalosVersion.value, '1.10.0'))
|
||||
const talosctlAvailable = computed(() => gte(resolvedTalosVersion.value, '1.11.0-alpha.3'))
|
||||
const { data: quirks } = useResourceGet<QuirksSpec>(() => ({
|
||||
runtime: Runtime.Omni,
|
||||
resource: {
|
||||
namespace: VirtualNamespace,
|
||||
type: QuirksType,
|
||||
id: resolvedTalosVersion.value,
|
||||
},
|
||||
}))
|
||||
|
||||
const supportsUnifiedInstaller = computed(() => quirks.value?.spec.supports_unified_installer)
|
||||
const talosctlAvailable = computed(() => quirks.value?.spec.supports_factory_talosctl)
|
||||
|
||||
const { data: features } = useFeatures()
|
||||
const imageDownloadDialog = useTemplateRef<HTMLDialogElement>('downloadImageDialog')
|
||||
@ -72,11 +83,7 @@ async function downloadImage(url: string) {
|
||||
|
||||
useEventListener(imageDownloadDialog, 'close', abortImageDownload)
|
||||
|
||||
const { downloads } = useTalosctlDownloads()
|
||||
|
||||
const talosctlPaths = computed(
|
||||
() => downloads.value?.get(`v${resolvedTalosVersion.value}`)?.map((v) => v.url) ?? [],
|
||||
)
|
||||
const { data: talosctlPaths } = useTalosctlDownloads(() => resolvedTalosVersion.value)
|
||||
|
||||
const { data: selectedCloudProvider } = useResourceGet<PlatformConfigSpec>(() => ({
|
||||
skip: formState.value.hardwareType !== 'cloud',
|
||||
|
||||
9
go.mod
9
go.mod
@ -74,12 +74,12 @@ require (
|
||||
github.com/siderolabs/go-retry v0.3.3
|
||||
github.com/siderolabs/go-talos-support v0.2.1
|
||||
github.com/siderolabs/grpc-proxy v0.5.2
|
||||
github.com/siderolabs/image-factory v1.1.0
|
||||
github.com/siderolabs/image-factory v1.2.0
|
||||
github.com/siderolabs/kms-client v0.2.0
|
||||
github.com/siderolabs/omni/client v1.6.5
|
||||
github.com/siderolabs/proto-codec v0.1.4
|
||||
github.com/siderolabs/siderolink v0.3.16
|
||||
github.com/siderolabs/talos/pkg/machinery v1.13.0-rc.0
|
||||
github.com/siderolabs/talos/pkg/machinery v1.13.0
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
@ -153,7 +153,7 @@ require (
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/creack/pty v1.1.21 // indirect
|
||||
github.com/creack/pty v1.1.24 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v29.4.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||
@ -181,6 +181,8 @@ require (
|
||||
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.1 // indirect
|
||||
github.com/go-openapi/testify/v2 v2.4.1 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
@ -189,6 +191,7 @@ require (
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/cel-go v0.28.0 // indirect
|
||||
github.com/google/gnostic-models v0.7.1 // indirect
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/gosuri/uilive v0.0.4 // indirect
|
||||
|
||||
24
go.sum
24
go.sum
@ -128,8 +128,8 @@ github.com/cosi-project/state-sqlite v0.4.0/go.mod h1:V20oy2Sfxla0zZ+SJSgjV20feg
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -220,10 +220,10 @@ github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzz
|
||||
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
|
||||
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
|
||||
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.1 h1:NZOrZmIb6PTv5LTFxr5/mKV/FjbUzGE7E6gLz7vFoOQ=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.1/go.mod h1:r7dwsujEHawapMsxA69i+XMGZrQ5tRauhLAjV/sxg3Q=
|
||||
github.com/go-openapi/testify/v2 v2.4.1 h1:zB34HDKj4tHwyUQHrUkpV0Q0iXQ6dUCOQtIqn8hE6Iw=
|
||||
github.com/go-openapi/testify/v2 v2.4.1/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
@ -260,8 +260,8 @@ github.com/google/go-containerregistry v0.21.5/go.mod h1:ySvMuiWg+dOsRW0Hw8GYwfM
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
@ -478,8 +478,8 @@ github.com/siderolabs/go-talos-support v0.2.1 h1:QVFhcmGw1ZTTXEtukBgrdjNxYbsPAOT
|
||||
github.com/siderolabs/go-talos-support v0.2.1/go.mod h1:tfwP9mpPdmqLU8DuCDgcAd0ficLlJuB/XMtrIV8g+20=
|
||||
github.com/siderolabs/grpc-proxy v0.5.2 h1:o34tS02IxbX3qeXe0MM99zE2XhfWHuvdBtSWWRD9HNI=
|
||||
github.com/siderolabs/grpc-proxy v0.5.2/go.mod h1:ygf+gqFxWdymAXuP4qMHTa+j6SvasdcFg3XkhpvUvDw=
|
||||
github.com/siderolabs/image-factory v1.1.0 h1:M5OZXPKQtBlUbzzH9PSP+ydp75S3mdYyiLXo7BoX9mc=
|
||||
github.com/siderolabs/image-factory v1.1.0/go.mod h1:cequkUpOoM+D1fA5kQrs573aLFwoqTPMAAeA1N1poog=
|
||||
github.com/siderolabs/image-factory v1.2.0 h1:5/VbxTemmzBM6QFuh0VenMr9nZCblGp9JmrWeXQTwbI=
|
||||
github.com/siderolabs/image-factory v1.2.0/go.mod h1:nAE1u+vYMK711Ktt8RshYW6S2zWNQ8XUAG7lMWeIZ7I=
|
||||
github.com/siderolabs/kms-client v0.2.0 h1:8RniCStUI75RTZO8qkhHOSVOnEU1AvvsKqJ7FqW/8NA=
|
||||
github.com/siderolabs/kms-client v0.2.0/go.mod h1:qq6dwcLPO0gaUyfkrhWi/37g/ZyZJzOHzvHrilLz48E=
|
||||
github.com/siderolabs/net v0.4.0 h1:1bOgVay/ijPkJz4qct98nHsiB/ysLQU0KLoBC4qLm7I=
|
||||
@ -490,8 +490,8 @@ github.com/siderolabs/protoenc v0.2.4 h1:D3Fpn2nQSQOhl8ZlAxijZAf7K6F8CM1uZq0afIG
|
||||
github.com/siderolabs/protoenc v0.2.4/go.mod h1:i5XLHjfv5vyi7LhQrSEo19HCA+lYtDd7CWxsoWp9XE8=
|
||||
github.com/siderolabs/siderolink v0.3.16 h1:DZzf3rzE/5hm4s9yfaYnoR2cy0od+oNKMx0GEpCxrfw=
|
||||
github.com/siderolabs/siderolink v0.3.16/go.mod h1:/05gMCYn81gCRZcmWlDbKwuy2800so1Mwd1CP1h3m4g=
|
||||
github.com/siderolabs/talos/pkg/machinery v1.13.0-rc.0 h1:sM0w/8BwBy6dVOu4XLW8m9xhA2iMmrmME3zpyFDHdwc=
|
||||
github.com/siderolabs/talos/pkg/machinery v1.13.0-rc.0/go.mod h1:70Up2PI+g6wxW4rJ8AIlZO0MfQ8gglyfqsyBv0PdnSo=
|
||||
github.com/siderolabs/talos/pkg/machinery v1.13.0 h1:nNfAUqgD/yOb4RZAc3xrQXKYllIK36RPaJacNQQC3TI=
|
||||
github.com/siderolabs/talos/pkg/machinery v1.13.0/go.mod h1:70Up2PI+g6wxW4rJ8AIlZO0MfQ8gglyfqsyBv0PdnSo=
|
||||
github.com/siderolabs/tcpproxy v0.1.0 h1:IbkS9vRhjMOscc1US3M5P1RnsGKFgB6U5IzUk+4WkKA=
|
||||
github.com/siderolabs/tcpproxy v0.1.0/go.mod h1:onn6CPPj/w1UNqQ0U97oRPF0CqbrgEApYCw4P9IiCW8=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
|
||||
@ -509,6 +509,7 @@ func filterAccess(ctx context.Context, access state.Access) error {
|
||||
virtual.AdvertisedEndpointsType,
|
||||
virtual.CurrentUserType,
|
||||
virtual.ClusterPermissionsType,
|
||||
virtual.QuirksType,
|
||||
virtual.PermissionsType:
|
||||
// allow access with just valid signature
|
||||
_, err = auth.CheckGRPC(ctx, auth.WithValidSignature(true))
|
||||
@ -656,6 +657,7 @@ func filterAccessByType(access state.Access) error {
|
||||
virtual.PermissionsType,
|
||||
virtual.KubernetesUsageType,
|
||||
virtual.LabelsCompletionType,
|
||||
virtual.QuirksType,
|
||||
virtual.ClusterPermissionsType:
|
||||
// allow read access only. these resources are either managed by controllers or plugins (e.g., infra provider plugins)
|
||||
if access.Verb.Readonly() {
|
||||
|
||||
@ -18,12 +18,14 @@ import (
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/safe"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
|
||||
"github.com/stripe/stripe-go/v85"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/siderolabs/omni/client/api/omni/specs"
|
||||
"github.com/siderolabs/omni/client/pkg/omni/resources"
|
||||
authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth"
|
||||
omniresources "github.com/siderolabs/omni/client/pkg/omni/resources/omni"
|
||||
"github.com/siderolabs/omni/client/pkg/omni/resources/virtual"
|
||||
stateerrors "github.com/siderolabs/omni/internal/backend/runtime/omni/virtual/pkg/errors"
|
||||
"github.com/siderolabs/omni/internal/backend/runtime/omni/virtual/pkg/factory/configs"
|
||||
@ -126,6 +128,8 @@ func (v *State) Get(ctx context.Context, ptr resource.Pointer, opts ...state.Get
|
||||
return v.advertisedEndpoints(ctx, ptr)
|
||||
case virtual.SupportType:
|
||||
return v.support(ctx, ptr)
|
||||
case virtual.QuirksType:
|
||||
return v.quirks(ctx, ptr)
|
||||
default:
|
||||
return nil, stateerrors.ErrUnsupported(fmt.Errorf("unsupported resource type for get %q", ptr.Type()))
|
||||
}
|
||||
@ -174,6 +178,8 @@ func (v *State) List(ctx context.Context, kind resource.Kind, opts ...state.List
|
||||
}
|
||||
|
||||
return resource.List{Items: []resource.Resource{permissions}}, nil
|
||||
case virtual.QuirksType:
|
||||
return v.listQuirks(ctx)
|
||||
default:
|
||||
return resource.List{}, stateerrors.ErrUnsupported(fmt.Errorf("unsupported resource type for list %q", kind.Type()))
|
||||
}
|
||||
@ -424,6 +430,45 @@ func (v *State) support(ctx context.Context, ptr resource.Pointer) (*virtual.Sup
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (v *State) listQuirks(ctx context.Context) (resource.List, error) {
|
||||
list, err := v.PrimaryState.List(ctx, resource.NewMetadata(resources.DefaultNamespace, omniresources.TalosVersionType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return resource.List{}, err
|
||||
}
|
||||
|
||||
items := make([]resource.Resource, 0, len(list.Items))
|
||||
|
||||
for _, item := range list.Items {
|
||||
q, err := v.quirks(ctx, item.Metadata())
|
||||
if err != nil {
|
||||
return resource.List{}, err
|
||||
}
|
||||
|
||||
items = append(items, q)
|
||||
}
|
||||
|
||||
return resource.List{Items: items}, nil
|
||||
}
|
||||
|
||||
func (v *State) quirks(_ context.Context, ptr resource.Pointer) (*virtual.Quirks, error) {
|
||||
res := virtual.NewQuirks(ptr.ID())
|
||||
|
||||
version, err := resource.ParseVersion("1")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := quirks.New(ptr.ID())
|
||||
|
||||
res.Metadata().SetVersion(version)
|
||||
res.TypedSpec().Value = &specs.QuirksSpec{
|
||||
SupportsUnifiedInstaller: q.SupportsUnifiedInstaller(),
|
||||
SupportsFactoryTalosctl: q.SupportsFactoryTalosctlDownload(),
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func fetchSupportEnabled(ctx context.Context, stripeClient *stripe.Client, stripeSubscriptionItemID string, eligibleProducts []string) (bool, error) {
|
||||
params := &stripe.SubscriptionItemRetrieveParams{}
|
||||
params.AddExpand("price.product")
|
||||
|
||||
@ -21,10 +21,10 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
coidc "github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/cosi-project/runtime/api/v1alpha1"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
@ -351,6 +351,7 @@ func (s *Server) makeMux(oidcProvider *oidc.Provider) (*http.ServeMux, error) {
|
||||
samlHandler,
|
||||
s.state,
|
||||
s.omniRuntime,
|
||||
s.imageFactoryClient,
|
||||
s.logger,
|
||||
s.cfg,
|
||||
)
|
||||
@ -857,6 +858,7 @@ func makeMux(
|
||||
samlHandler *samlsp.Middleware,
|
||||
state *omni.State,
|
||||
omniRuntime *omni.Runtime,
|
||||
imageFactoryClient *imagefactory.Client,
|
||||
logger *zap.Logger,
|
||||
cfg *config.Params,
|
||||
) (*http.ServeMux, error) {
|
||||
@ -895,14 +897,14 @@ func makeMux(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
talosctlHandler, err := makeTalosctlHandler(state.Default(), logger)
|
||||
talosctlHandler, err := makeTalosctlHandler(imageFactoryClient, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
muxHandle("/exposed/service", workloadProxyRedirect, "exposed-service-redirect")
|
||||
muxHandle("/omnictl/", http.StripPrefix("/omnictl/", omnictlHndlr), "files")
|
||||
muxHandle("/talosctl/downloads", talosctlHandler, "talosctl-downloads")
|
||||
muxHandle("/talosctl/downloads/{version}", talosctlHandler, "talosctl-downloads")
|
||||
// actually enabled only in debug build
|
||||
muxHandle("/debug/", debug.NewHandler(omniRuntime.GetCOSIRuntime(), state.Default()), "debug")
|
||||
|
||||
@ -1249,14 +1251,14 @@ func runPprofServer(ctx context.Context, bindAddress string, l *zap.Logger) erro
|
||||
}
|
||||
|
||||
//nolint:unparam
|
||||
func makeTalosctlHandler(state state.State, logger *zap.Logger) (http.Handler, error) {
|
||||
func makeTalosctlHandler(imageFactoryClient *imagefactory.Client, logger *zap.Logger) (http.Handler, error) {
|
||||
// The list of versions does not update very often, so we can cache it.
|
||||
cacher := cache.Value[releaseData]{Duration: time.Hour}
|
||||
var cacherMap sync.Map
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
type result struct {
|
||||
ReleaseData *releaseData `json:"release_data,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Status string `json:"status"`
|
||||
Downloads []string `json:"downloads,omitempty"`
|
||||
}
|
||||
|
||||
writeResult := func(a any, code int) {
|
||||
@ -1268,9 +1270,21 @@ func makeTalosctlHandler(state state.State, logger *zap.Logger) (http.Handler, e
|
||||
}
|
||||
}
|
||||
|
||||
talosVersion := r.PathValue("version")
|
||||
|
||||
actual, _ := cacherMap.LoadOrStore(talosVersion, &cache.Value[[]string]{Duration: time.Hour})
|
||||
|
||||
cacher, ok := actual.(*cache.Value[[]string])
|
||||
if !ok {
|
||||
logger.Error("failed to load version cache")
|
||||
writeResult(result{Status: "failed to load version cache"}, http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx := actor.MarkContextAsInternalActor(r.Context())
|
||||
|
||||
data, err := cacher.GetOrUpdate(func() (releaseData, error) { return getReleaseData(ctx, state) })
|
||||
data, err := cacher.GetOrUpdate(func() ([]string, error) { return imageFactoryClient.TalosctlList(ctx, talosVersion) })
|
||||
if err != nil {
|
||||
logger.Error("failed to get latest talosctl release", zap.Error(err))
|
||||
writeResult(result{Status: "failed to get latest talosctl release"}, http.StatusInternalServerError)
|
||||
@ -1279,140 +1293,12 @@ func makeTalosctlHandler(state state.State, logger *zap.Logger) (http.Handler, e
|
||||
}
|
||||
|
||||
writeResult(result{
|
||||
ReleaseData: &data,
|
||||
Status: "ok",
|
||||
Status: "ok",
|
||||
Downloads: data,
|
||||
}, http.StatusOK)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func getReleaseData(ctx context.Context, state state.State) (releaseData, error) {
|
||||
all, err := safe.StateListAll[*omnires.TalosVersion](ctx, state)
|
||||
if err != nil {
|
||||
return releaseData{}, fmt.Errorf("failed to list all talos versions: %w", err)
|
||||
}
|
||||
|
||||
if all.Len() == 0 {
|
||||
return releaseData{}, errors.New("no talos versions found")
|
||||
}
|
||||
|
||||
versionNames := make([]string, 0, all.Len())
|
||||
|
||||
for val := range all.All() {
|
||||
version := val.TypedSpec().Value.Version
|
||||
if !strings.HasPrefix(version, "v") {
|
||||
version = "v" + version
|
||||
}
|
||||
|
||||
versionNames = append(versionNames, version)
|
||||
}
|
||||
|
||||
releases, err := getGithubReleases(versionNames...)
|
||||
if err != nil {
|
||||
return releaseData{}, err
|
||||
}
|
||||
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
func getGithubReleases(tags ...string) (releaseData, error) {
|
||||
if len(tags) == 0 {
|
||||
return releaseData{}, errors.New("no tags provided")
|
||||
}
|
||||
|
||||
versions := make(map[string][]talosctlAsset, len(tags))
|
||||
|
||||
for _, tag := range tags {
|
||||
assets := make([]talosctlAsset, 0, len(assetsData))
|
||||
|
||||
for _, asset := range assetsData {
|
||||
if asset.minTalosVersion != "" {
|
||||
v, err := semver.ParseTolerant(tag)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if v.LT(semver.MustParse(asset.minTalosVersion)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
assets = append(assets, talosctlAsset{
|
||||
Name: asset.name,
|
||||
URL: fmt.Sprintf("https://github.com/siderolabs/talos/releases/download/%s/%s", tag, asset.urlPart),
|
||||
})
|
||||
}
|
||||
|
||||
versions[tag] = assets
|
||||
}
|
||||
|
||||
return releaseData{
|
||||
AvailableVersions: versions,
|
||||
DefaultVersion: tags[len(tags)-1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
type releaseData struct {
|
||||
AvailableVersions map[string][]talosctlAsset `json:"available_versions"`
|
||||
DefaultVersion string `json:"default_version,omitempty"`
|
||||
}
|
||||
|
||||
type talosctlAsset struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
var assetsData = []struct {
|
||||
name string
|
||||
urlPart string
|
||||
minTalosVersion string
|
||||
}{
|
||||
{
|
||||
"Apple",
|
||||
"talosctl-darwin-amd64",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Apple Silicon",
|
||||
"talosctl-darwin-arm64",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"FreeBSD",
|
||||
"talosctl-freebsd-amd64",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"FreeBSD ARM64",
|
||||
"talosctl-freebsd-arm64",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Linux",
|
||||
"talosctl-linux-amd64",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Linux ARM",
|
||||
"talosctl-linux-armv7",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Linux ARM64",
|
||||
"talosctl-linux-arm64",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Windows",
|
||||
"talosctl-windows-amd64.exe",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Windows ARM64",
|
||||
"talosctl-windows-arm64.exe",
|
||||
"1.9.0",
|
||||
},
|
||||
}
|
||||
|
||||
// Auditor is a common interface for audit log.
|
||||
type Auditor interface {
|
||||
RunCleanup(context.Context) error
|
||||
|
||||
@ -1234,6 +1234,11 @@ func AssertResourceAuthz(rootCtx context.Context, rootCli *client.Client, client
|
||||
resource: virtual.NewSupport(),
|
||||
allowedVerbSet: readOnlyVerbSet,
|
||||
},
|
||||
{
|
||||
resource: virtual.NewQuirks(uuid.New().String()),
|
||||
allowedVerbSet: readOnlyVerbSet,
|
||||
isSignatureSufficient: true,
|
||||
},
|
||||
{
|
||||
resource: omni.NewEtcdBackupStatus(uuid.New().String()),
|
||||
allowedVerbSet: readOnlyVerbSet,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user