diff --git a/cmd/metal-api/internal/service/ip-service_test.go b/cmd/metal-api/internal/service/ip-service_test.go index 9b0b9c7c4..c165f5163 100644 --- a/cmd/metal-api/internal/service/ip-service_test.go +++ b/cmd/metal-api/internal/service/ip-service_test.go @@ -330,7 +330,6 @@ func TestUpdateIP(t *testing.T) { wantedIPBase: &v1.IPBase{ ProjectID: testdata.IP1.ProjectID, Type: "static", - Tags: []string{}, }, }, { diff --git a/cmd/metal-api/internal/service/machine-service_test.go b/cmd/metal-api/internal/service/machine-service_test.go index 1f13b4910..39bb72c07 100644 --- a/cmd/metal-api/internal/service/machine-service_test.go +++ b/cmd/metal-api/internal/service/machine-service_test.go @@ -8,8 +8,14 @@ import ( "net/http" "net/http/httptest" "testing" + "time" + restfulspec "github.com/emicklei/go-restful-openapi/v2" "github.com/emicklei/go-restful/v3" + "github.com/go-openapi/spec" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" + "github.com/google/go-cmp/cmp" goipam "github.com/metal-stack/go-ipam" "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore" "github.com/metal-stack/metal-api/cmd/metal-api/internal/ipam" @@ -17,6 +23,7 @@ import ( v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1" "github.com/metal-stack/metal-api/cmd/metal-api/internal/testdata" "github.com/metal-stack/metal-lib/bus" + "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/metal-stack/security" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" @@ -1339,3 +1346,417 @@ func Test_gatherNetworksFromSpec(t *testing.T) { }) } } + +func TestNewMachineResponse(t *testing.T) { + tests := []struct { + name string + m *metal.Machine + s *metal.Size + p *metal.Partition + i *metal.Image + ec *metal.ProvisioningEventContainer + want *v1.MachineResponse + }{ + { + name: "test zero values", + m: &metal.Machine{ + Base: metal.Base{ + ID: "", + Name: "", + Description: "", + Created: time.Time{}, + Changed: time.Time{}, + }, + Allocation: &metal.MachineAllocation{ + Creator: "", + Created: time.Time{}, + Name: "", + Description: "", + Project: "", + ImageID: "", + FilesystemLayout: &metal.FilesystemLayout{ + Base: metal.Base{ + ID: "", + Name: "", + Description: "", + Created: time.Time{}, + Changed: time.Time{}, + }, + Filesystems: []metal.Filesystem{}, + Disks: []metal.Disk{}, + Raid: []metal.Raid{}, + VolumeGroups: []metal.VolumeGroup{}, + LogicalVolumes: []metal.LogicalVolume{}, + Constraints: metal.FilesystemLayoutConstraints{}, + }, + MachineNetworks: []*metal.MachineNetwork{}, + Hostname: "", + SSHPubKeys: []string{}, + UserData: "", + ConsolePassword: "", + Succeeded: false, + Reinstall: false, + MachineSetup: &metal.MachineSetup{ + ImageID: "", + PrimaryDisk: "", + OSPartition: "", + Initrd: "", + Cmdline: "", + Kernel: "", + BootloaderID: "", + }, + Role: metal.RoleFirewall, + VPN: &metal.MachineVPN{ + ControlPlaneAddress: "", + AuthKey: "", + Connected: false, + }, + UUID: "", + FirewallRules: &metal.FirewallRules{ + Egress: []metal.EgressRule{}, + Ingress: []metal.IngressRule{}, + }, + }, + PartitionID: "", + SizeID: "", + RackID: "", + Waiting: false, + PreAllocated: false, + Hardware: metal.MachineHardware{ + Memory: 0, + CPUCores: 0, + Nics: []metal.Nic{}, + Disks: []metal.BlockDevice{}, + }, + State: metal.MachineState{ + Value: "", + Description: "", + Issuer: "", + MetalHammerVersion: "", + }, + LEDState: metal.ChassisIdentifyLEDState{ + Value: "", + Description: "", + }, + Tags: []string{}, + IPMI: metal.IPMI{ + Address: "", + MacAddress: "", + User: "", + Password: "", + Interface: "", + Fru: metal.Fru{ + ChassisPartNumber: "", + ChassisPartSerial: "", + BoardMfg: "", + BoardMfgSerial: "", + BoardPartNumber: "", + ProductManufacturer: "", + ProductPartNumber: "", + ProductSerial: "", + }, + BMCVersion: "", + PowerState: "", + PowerMetric: &metal.PowerMetric{ + AverageConsumedWatts: 0, + IntervalInMin: 0, + MaxConsumedWatts: 0, + MinConsumedWatts: 0, + }, + LastUpdated: time.Time{}, + }, + BIOS: metal.BIOS{ + Version: "", + Vendor: "", + Date: "", + }, + }, + want: &v1.MachineResponse{ + Common: v1.Common{ + Describable: v1.Describable{ + Name: pointer.Pointer(""), + Description: pointer.Pointer(""), + }, + }, + MachineBase: v1.MachineBase{ + Allocation: &v1.MachineAllocation{ + FilesystemLayout: &v1.FilesystemLayoutResponse{ + Common: v1.Common{ + Describable: v1.Describable{ + Name: pointer.Pointer(""), + Description: pointer.Pointer(""), + }, + }, + }, + BootInfo: &v1.BootInfo{}, + Role: "firewall", + VPN: &v1.MachineVPN{}, + FirewallRules: &v1.FirewallRules{}, + }, + Hardware: v1.MachineHardware{}, + }, + }, + }, + { + name: "test zero values, slices expanded", + m: &metal.Machine{ + Base: metal.Base{ + ID: "", + Name: "", + Description: "", + Created: time.Time{}, + Changed: time.Time{}, + }, + Allocation: &metal.MachineAllocation{ + Creator: "", + Created: time.Time{}, + Name: "", + Description: "", + Project: "", + ImageID: "", + FilesystemLayout: &metal.FilesystemLayout{ + Base: metal.Base{ + ID: "", + Name: "", + Description: "", + Created: time.Time{}, + Changed: time.Time{}, + }, + Filesystems: []metal.Filesystem{{}}, + Disks: []metal.Disk{{}}, + Raid: []metal.Raid{{}}, + VolumeGroups: []metal.VolumeGroup{{}}, + LogicalVolumes: []metal.LogicalVolume{{}}, + Constraints: metal.FilesystemLayoutConstraints{}, + }, + MachineNetworks: []*metal.MachineNetwork{{}}, + Hostname: "", + SSHPubKeys: []string{}, + UserData: "", + ConsolePassword: "", + Succeeded: false, + Reinstall: false, + MachineSetup: &metal.MachineSetup{ + ImageID: "", + PrimaryDisk: "", + OSPartition: "", + Initrd: "", + Cmdline: "", + Kernel: "", + BootloaderID: "", + }, + Role: metal.RoleFirewall, + VPN: &metal.MachineVPN{ + ControlPlaneAddress: "", + AuthKey: "", + Connected: false, + }, + UUID: "", + FirewallRules: &metal.FirewallRules{ + Egress: []metal.EgressRule{{}}, + Ingress: []metal.IngressRule{{}}, + }, + }, + PartitionID: "", + SizeID: "", + RackID: "", + Waiting: false, + PreAllocated: false, + Hardware: metal.MachineHardware{ + Memory: 0, + CPUCores: 0, + Nics: []metal.Nic{{}}, + Disks: []metal.BlockDevice{{}}, + }, + State: metal.MachineState{ + Value: "", + Description: "", + Issuer: "", + MetalHammerVersion: "", + }, + LEDState: metal.ChassisIdentifyLEDState{ + Value: "", + Description: "", + }, + Tags: []string{}, + IPMI: metal.IPMI{ + Address: "", + MacAddress: "", + User: "", + Password: "", + Interface: "", + Fru: metal.Fru{ + ChassisPartNumber: "", + ChassisPartSerial: "", + BoardMfg: "", + BoardMfgSerial: "", + BoardPartNumber: "", + ProductManufacturer: "", + ProductPartNumber: "", + ProductSerial: "", + }, + BMCVersion: "", + PowerState: "", + PowerMetric: &metal.PowerMetric{ + AverageConsumedWatts: 0, + IntervalInMin: 0, + MaxConsumedWatts: 0, + MinConsumedWatts: 0, + }, + LastUpdated: time.Time{}, + }, + BIOS: metal.BIOS{ + Version: "", + Vendor: "", + Date: "", + }, + }, + want: &v1.MachineResponse{ + Common: v1.Common{ + Describable: v1.Describable{ + Name: pointer.Pointer(""), + Description: pointer.Pointer(""), + }, + }, + MachineBase: v1.MachineBase{ + Allocation: &v1.MachineAllocation{ + FilesystemLayout: &v1.FilesystemLayoutResponse{ + Common: v1.Common{Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}}, + FilesystemLayoutBase: v1.FilesystemLayoutBase{ + Filesystems: []v1.Filesystem{{}}, + Disks: []v1.Disk{{}}, + Raid: []v1.Raid{{}}, + VolumeGroups: []v1.VolumeGroup{{}}, + LogicalVolumes: []v1.LogicalVolume{{}}, + Constraints: v1.FilesystemLayoutConstraints{}, + }, + }, + BootInfo: &v1.BootInfo{}, + Role: "firewall", + VPN: &v1.MachineVPN{}, + FirewallRules: &v1.FirewallRules{ + Egress: []v1.FirewallEgressRule{{}}, + Ingress: []v1.FirewallIngressRule{{}}, + }, + MachineNetworks: []v1.MachineNetwork{{ + NetworkType: "external", + }}, + }, + Hardware: v1.MachineHardware{ + MachineHardwareBase: v1.MachineHardwareBase{ + Disks: []v1.MachineBlockDevice{{}}, + }, + Nics: v1.MachineNics{{}}, + }, + }, + }, + }, + { + name: "test firewall response", + m: &testdata.FW1, + s: &testdata.Sz1, + p: &testdata.Partition1, + i: &testdata.Img1, + ec: &metal.ProvisioningEventContainer{}, + want: &v1.MachineResponse{ + Common: v1.Common{ + Identifiable: v1.Identifiable{ + ID: testdata.FW1.ID, + }, + Describable: v1.Describable{ + Name: pointer.Pointer(""), + Description: pointer.Pointer(""), + }, + }, + MachineBase: v1.MachineBase{ + Partition: v1.NewPartitionResponse(&testdata.Partition1), + RackID: "", + Size: v1.NewSizeResponse(&testdata.Sz1), + Hardware: v1.MachineHardware{ + MachineHardwareBase: v1.MachineHardwareBase{ + Memory: testdata.FW1.Hardware.Memory, + CPUCores: testdata.FW1.Hardware.CPUCores, + Disks: []v1.MachineBlockDevice{ + { + Size: testdata.FW1.Hardware.Disks[0].Size, + }, + { + Size: testdata.FW1.Hardware.Disks[1].Size, + }, + { + Size: testdata.FW1.Hardware.Disks[2].Size, + }, + }, + }, + }, + Allocation: &v1.MachineAllocation{ + Name: testdata.FW1.Allocation.Name, + Project: testdata.FW1.Allocation.Project, + Image: v1.NewImageResponse(&testdata.Img1), + MachineNetworks: []v1.MachineNetwork{ + { + NetworkType: "privateprimaryunshared", + Vrf: 1, + Private: true, + }, + }, + Role: "firewall", + FirewallRules: &v1.FirewallRules{ + Egress: []v1.FirewallEgressRule{ + { + Protocol: "tcp", + Ports: []int{443}, + To: []string{"0.0.0.0/0"}, + Comment: "test", + }, + }, + Ingress: nil, + }, + }, + RecentProvisioningEvents: v1.MachineRecentProvisioningEvents{}, + Tags: testdata.FW1.Tags, + }, + Timestamps: v1.Timestamps{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := v1.NewMachineResponse(tt.m, tt.s, tt.p, tt.i, tt.ec) + if diff := cmp.Diff(got, tt.want); diff != "" { + t.Errorf("diff (-want +got):\n%s", diff) + } + + ws, err := NewMachine(slog.Default(), nil, &emptyPublisher{}, bus.DirectEndpoints(), ipam.New(nil), nil, nil, nil, 0, nil, metal.DisabledIPMISuperUser()) + require.NoError(t, err) + + validateAgainstSwaggerSpec(t, ws, "v1.MachineResponse", got) + }) + } +} + +func validateAgainstSwaggerSpec(t *testing.T, ws *restful.WebService, definitionKey string, obj any) { + container := restful.NewContainer() + container.Add(ws) + + actual := restfulspec.BuildSwagger(restfulspec.Config{ + WebServices: container.RegisteredWebServices(), + }) + + schemaJSON, err := json.MarshalIndent(actual, "", " ") + require.NoError(t, err) + + schema := new(spec.Schema) + err = json.Unmarshal(schemaJSON, schema) + require.NoError(t, err) + + // you need to pass the definition of the object to the validator otherwise it will not find any problems + def, ok := schema.Definitions[definitionKey] + require.True(t, ok) + + // we now put the entire defintions in the specific definition such that references can be resolved + def.Definitions = schema.Definitions + + err = validate.AgainstSchema(&def, obj, strfmt.Default, validate.EnableArrayMustHaveItemsCheck(true), validate.EnableObjectArrayTypeCheck(true)) + require.NoError(t, err) +} diff --git a/cmd/metal-api/internal/service/v1/filesystem.go b/cmd/metal-api/internal/service/v1/filesystem.go index dd469f4dd..23bbee0d0 100644 --- a/cmd/metal-api/internal/service/v1/filesystem.go +++ b/cmd/metal-api/internal/service/v1/filesystem.go @@ -4,11 +4,11 @@ import "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" type ( FilesystemLayoutBase struct { - Filesystems []Filesystem `json:"filesystems" description:"list of filesystems to create" optional:"true"` - Disks []Disk `json:"disks" description:"list of disks that belong to this layout" optional:"true"` - Raid []Raid `json:"raid" description:"list of raid arrays to create" optional:"true"` - VolumeGroups []VolumeGroup `json:"volumegroups" description:"list of volumegroups to create" optional:"true"` - LogicalVolumes []LogicalVolume `json:"logicalvolumes" description:"list of logicalvolumes to create" optional:"true"` + Filesystems []Filesystem `json:"filesystems,omitempty" description:"list of filesystems to create" optional:"true"` + Disks []Disk `json:"disks,omitempty" description:"list of disks that belong to this layout" optional:"true"` + Raid []Raid `json:"raid,omitempty" description:"list of raid arrays to create" optional:"true"` + VolumeGroups []VolumeGroup `json:"volumegroups,omitempty" description:"list of volumegroups to create" optional:"true"` + LogicalVolumes []LogicalVolume `json:"logicalvolumes,omitempty" description:"list of logicalvolumes to create" optional:"true"` Constraints FilesystemLayoutConstraints `json:"constraints" description:"constraints which must match that this layout is taken, if sizes and images are empty these are develop layouts"` } FilesystemLayoutResponse struct { @@ -37,27 +37,27 @@ type ( } FilesystemLayoutConstraints struct { - Sizes []string `json:"sizes" description:"list of sizes this layout applies to" optional:"true"` - Images map[string]string `json:"images" description:"list of images this layout applies to"` + Sizes []string `json:"sizes,omitempty" description:"list of sizes this layout applies to" optional:"true"` + Images map[string]string `json:"images,omitempty" description:"list of images this layout applies to"` } Filesystem struct { - Path *string `json:"path" description:"the mountpoint where this filesystem should be mounted on" optional:"true"` + Path *string `json:"path,omitempty" description:"the mountpoint where this filesystem should be mounted on" optional:"true"` Device string `json:"device" description:"the underlaying device where this filesystem should be created"` Format string `json:"format" description:"the filesystem format"` - Label *string `json:"label" description:"optional label for this this filesystem" optional:"true"` - MountOptions []string `json:"mountoptions" description:"the options to use to mount this filesystem" optional:"true"` - CreateOptions []string `json:"createoptions" description:"the options to use to create (mkfs) this filesystem" optional:"true"` + Label *string `json:"label,omitempty" description:"optional label for this this filesystem" optional:"true"` + MountOptions []string `json:"mountoptions,omitempty" description:"the options to use to mount this filesystem" optional:"true"` + CreateOptions []string `json:"createoptions,omitempty" description:"the options to use to create (mkfs) this filesystem" optional:"true"` } Disk struct { Device string `json:"device" description:"the device to create the partitions"` - Partitions []DiskPartition `json:"partitions" description:"list of partitions to create on this disk" optional:"true"` + Partitions []DiskPartition `json:"partitions,omitempty" description:"list of partitions to create on this disk" optional:"true"` WipeOnReinstall bool `json:"wipeonreinstall" description:"if set to true, this disk will be wiped before reinstallation"` } Raid struct { ArrayName string `json:"arrayname" description:"the name of the resulting array device"` - Devices []string `json:"devices" description:"list of devices to form the raid array from" optional:"true"` + Devices []string `json:"devices,omitempty" description:"list of devices to form the raid array from" optional:"true"` Level string `json:"level" description:"raid level to create, should be 0 or 1"` - CreateOptions []string `json:"createoptions" description:"the options to use to create the raid array" optional:"true"` + CreateOptions []string `json:"createoptions,omitempty" description:"the options to use to create the raid array" optional:"true"` Spares int `json:"spares" description:"number of spares for the raid array"` } DiskPartition struct { @@ -68,8 +68,8 @@ type ( } VolumeGroup struct { Name string `json:"name" description:"the name of the resulting volume group"` - Devices []string `json:"devices" description:"list of devices to form the volume group from" optional:"true"` - Tags []string `json:"tags" description:"list of tags to add to the volume group" optional:"true"` + Devices []string `json:"devices,omitempty" description:"list of devices to form the volume group from" optional:"true"` + Tags []string `json:"tags,omitempty" description:"list of tags to add to the volume group" optional:"true"` } LogicalVolume struct { diff --git a/cmd/metal-api/internal/service/v1/firewall.go b/cmd/metal-api/internal/service/v1/firewall.go index 727d6e1dc..0d12cd145 100644 --- a/cmd/metal-api/internal/service/v1/firewall.go +++ b/cmd/metal-api/internal/service/v1/firewall.go @@ -11,16 +11,16 @@ type FirewallAllocateRequest struct { type FirewallEgressRule struct { Protocol string `json:"protocol,omitempty" description:"the protocol for the rule, defaults to tcp" enum:"tcp|udp" optional:"true"` - Ports []int `json:"ports" description:"the ports affected by this rule"` - To []string `json:"to" description:"the cidrs affected by this rule"` + Ports []int `json:"ports,omitempty" description:"the ports affected by this rule"` + To []string `json:"to,omitempty" description:"the cidrs affected by this rule"` Comment string `json:"comment,omitempty" description:"an optional comment describing what this rule is used for" optional:"true"` } type FirewallIngressRule struct { Protocol string `json:"protocol,omitempty" description:"the protocol for the rule, defaults to tcp" enum:"tcp|udp" optional:"true"` - Ports []int `json:"ports" description:"the ports affected by this rule"` + Ports []int `json:"ports,omitempty" description:"the ports affected by this rule"` To []string `json:"to,omitempty" description:"the cidrs affected by this rule" optional:"true"` - From []string `json:"from" description:"the cidrs affected by this rule"` + From []string `json:"from,omitempty" description:"the cidrs affected by this rule"` Comment string `json:"comment,omitempty" description:"an optional comment describing what this rule is used for" optional:"true"` } diff --git a/cmd/metal-api/internal/service/v1/image.go b/cmd/metal-api/internal/service/v1/image.go index b4cf79a57..05755c372 100644 --- a/cmd/metal-api/internal/service/v1/image.go +++ b/cmd/metal-api/internal/service/v1/image.go @@ -9,10 +9,10 @@ import ( type ImageBase struct { URL *string `json:"url" modelDescription:"an image that can be attached to a machine" description:"the url of this image" optional:"true"` - Features []string `json:"features" description:"features of this image" optional:"true"` + Features []string `json:"features,omitempty" description:"features of this image" optional:"true"` ExpirationDate time.Time `json:"expirationDate" description:"expirationDate of this image" optional:"false"` Classification string `json:"classification" description:"classification of this image" optional:"true"` - UsedBy []string `json:"usedby" description:"machines where this image is in use" optional:"true"` + UsedBy []string `json:"usedby,omitempty" description:"machines where this image is in use" optional:"true"` } type ImageCreateRequest struct { diff --git a/cmd/metal-api/internal/service/v1/ip.go b/cmd/metal-api/internal/service/v1/ip.go index a09292670..c3d293658 100644 --- a/cmd/metal-api/internal/service/v1/ip.go +++ b/cmd/metal-api/internal/service/v1/ip.go @@ -9,7 +9,7 @@ type IPBase struct { ProjectID string `json:"projectid" description:"the project this ip address belongs to"` NetworkID string `json:"networkid" description:"the network this ip allocate request address belongs to"` Type metal.IPType `json:"type" enum:"static|ephemeral" description:"the ip type, ephemeral leads to automatic cleanup of the ip address, static will enable re-use of the ip at a later point in time"` - Tags []string `json:"tags" description:"free tags that you associate with this ip." optional:"true"` + Tags []string `json:"tags,omitempty" description:"free tags that you associate with this ip." optional:"true"` } type IPIdentifiable struct { diff --git a/cmd/metal-api/internal/service/v1/machine.go b/cmd/metal-api/internal/service/v1/machine.go index 6f2cca5a6..37842e38e 100644 --- a/cmd/metal-api/internal/service/v1/machine.go +++ b/cmd/metal-api/internal/service/v1/machine.go @@ -23,7 +23,7 @@ type MachineBase struct { LEDState ChassisIdentifyLEDState `json:"ledstate" rethinkdb:"ledstate" description:"the state of this chassis identify LED"` Liveliness string `json:"liveliness" description:"the liveliness of this machine"` RecentProvisioningEvents MachineRecentProvisioningEvents `json:"events" description:"recent events of this machine during provisioning"` - Tags []string `json:"tags" description:"tags for this machine"` + Tags []string `json:"tags,omitempty" description:"tags for this machine"` } type MachineAllocation struct { @@ -34,9 +34,9 @@ type MachineAllocation struct { Project string `json:"project" description:"the project id that this machine is assigned to" ` Image *ImageResponse `json:"image" description:"the image assigned to this machine" readOnly:"true" optional:"true"` FilesystemLayout *FilesystemLayoutResponse `json:"filesystemlayout" description:"filesystemlayout to create on this machine" optional:"true"` - MachineNetworks []MachineNetwork `json:"networks" description:"the networks of this machine"` + MachineNetworks []MachineNetwork `json:"networks,omitempty" description:"the networks of this machine"` Hostname string `json:"hostname" description:"the hostname which will be used when creating the machine"` - SSHPubKeys []string `json:"ssh_pub_keys" description:"the public ssh keys to access the machine with"` + SSHPubKeys []string `json:"ssh_pub_keys,omitempty" description:"the public ssh keys to access the machine with"` UserData string `json:"user_data,omitempty" description:"userdata to execute post installation tasks" optional:"true"` Succeeded bool `json:"succeeded" description:"if the allocation of the machine was successful, this is set to true"` Reinstall bool `json:"reinstall" description:"indicates whether to reinstall the machine"` @@ -64,9 +64,9 @@ type BootInfo struct { type MachineNetwork struct { NetworkID string `json:"networkid" description:"the networkID of the allocated machine in this vrf"` - Prefixes []string `json:"prefixes" description:"the prefixes of this network"` - IPs []string `json:"ips" description:"the ip addresses of the allocated machine in this vrf"` - DestinationPrefixes []string `json:"destinationprefixes" modelDescription:"prefixes that are reachable within this network" description:"the destination prefixes of this network"` + Prefixes []string `json:"prefixes,omitempty" description:"the prefixes of this network"` + IPs []string `json:"ips,omitempty" description:"the ip addresses of the allocated machine in this vrf"` + DestinationPrefixes []string `json:"destinationprefixes,omitempty" modelDescription:"prefixes that are reachable within this network" description:"the destination prefixes of this network"` NetworkType string `json:"networktype" description:"the network type, types can be looked up in the network package of metal-lib"` Vrf uint `json:"vrf" description:"the vrf of the allocated machine"` // Attention, uint32 is converted to integer by swagger which is int32 which is to small to hold a asn @@ -85,12 +85,12 @@ type MachineNetwork struct { type MachineHardwareBase struct { Memory uint64 `json:"memory" description:"the total memory of the machine"` CPUCores int `json:"cpu_cores" description:"the number of cpu cores"` - Disks []MachineBlockDevice `json:"disks" description:"the list of block devices of this machine"` + Disks []MachineBlockDevice `json:"disks,omitempty" description:"the list of block devices of this machine"` } type MachineHardware struct { MachineHardwareBase - Nics MachineNics `json:"nics" description:"the list of network interfaces of this machine"` + Nics MachineNics `json:"nics,omitempty" description:"the list of network interfaces of this machine"` } type MachineState struct { @@ -111,8 +111,8 @@ type MachineBlockDevice struct { } type MachineRecentProvisioningEvents struct { - Events []MachineProvisioningEvent `json:"log" description:"the log of recent machine provisioning events"` - LastEventTime *time.Time `json:"last_event_time" description:"the time where the last event was received" optional:"true"` + Events []MachineProvisioningEvent `json:"log,omitempty" description:"the log of recent machine provisioning events"` + LastEventTime *time.Time `json:"last_event_time,omitempty" description:"the time where the last event was received" optional:"true"` LastErrorEvent *MachineProvisioningEvent `json:"last_error_event,omitempty" description:"the last erroneous event received" optional:"true"` CrashLoop bool `json:"crash_loop" description:"indicates that machine is provisioning crash loop"` FailedMachineReclaim bool `json:"failed_machine_reclaim" description:"indicates that machine reclaim has failed"` @@ -137,7 +137,7 @@ type MachineNic struct { MacAddress string `json:"mac" description:"the mac address of this network interface"` Name string `json:"name" description:"the name of this network interface"` Identifier string `json:"identifier" description:"the unique identifier of this network interface"` - Neighbors MachineNics `json:"neighbors" description:"the neighbors visible to this network interface"` + Neighbors MachineNics `json:"neighbors,omitempty" description:"the neighbors visible to this network interface"` } type MachineBIOS struct { @@ -199,7 +199,7 @@ type MachineAllocateRequest struct { SizeID string `json:"sizeid" description:"the size id to assign this machine to"` ImageID string `json:"imageid" description:"the image id to assign this machine to"` FilesystemLayoutID *string `json:"filesystemlayoutid" description:"the filesystemlayout id to assing to this machine" optional:"true"` - SSHPubKeys []string `json:"ssh_pub_keys" description:"the public ssh keys to access the machine with"` + SSHPubKeys []string `json:"ssh_pub_keys,omitempty" description:"the public ssh keys to access the machine with"` UserData *string `json:"user_data" description:"cloud-init.io compatible userdata must be base64 encoded" optional:"true"` Tags []string `json:"tags" description:"tags for this machine" optional:"true"` Networks MachineAllocationNetworks `json:"networks" description:"the networks that this machine will be placed in." optional:"true"` @@ -308,7 +308,7 @@ type MachineIssue struct { } func NewMetalMachineHardware(r *MachineHardware) metal.MachineHardware { - nics := metal.Nics{} + var nics metal.Nics for i := range r.Nics { var neighbors metal.Nics for i2 := range r.Nics[i].Neighbors { @@ -453,17 +453,17 @@ func NewMachineIPMIResponse(m *metal.Machine, s *metal.Size, p *metal.Partition, func NewMachineResponse(m *metal.Machine, s *metal.Size, p *metal.Partition, i *metal.Image, ec *metal.ProvisioningEventContainer) *MachineResponse { var hardware MachineHardware - nics := MachineNics{} + var nics MachineNics for i := range m.Hardware.Nics { n := m.Hardware.Nics[i] - neighs := MachineNics{} + var neighs MachineNics for j := range n.Neighbors { neigh := n.Neighbors[j] neighs = append(neighs, MachineNic{ MacAddress: string(neigh.MacAddress), Name: neigh.Name, Identifier: neigh.Identifier, - Neighbors: MachineNics{}, + Neighbors: nil, }) } nic := MachineNic{ @@ -475,7 +475,7 @@ func NewMachineResponse(m *metal.Machine, s *metal.Size, p *metal.Partition, i * nics = append(nics, nic) } - disks := []MachineBlockDevice{} + var disks []MachineBlockDevice for i := range m.Hardware.Disks { disk := MachineBlockDevice{ Name: m.Hardware.Disks[i].Name, @@ -497,7 +497,7 @@ func NewMachineResponse(m *metal.Machine, s *metal.Size, p *metal.Partition, i * if m.Allocation != nil { var networks []MachineNetwork for _, nw := range m.Allocation.MachineNetworks { - ips := append([]string{}, nw.IPs...) + ips := nw.IPs nt, err := nw.NetworkType() if err != nil { continue @@ -552,6 +552,11 @@ func NewMachineResponse(m *metal.Machine, s *metal.Size, p *metal.Partition, i * } } + var sshPubKeys []string + if len(m.Allocation.SSHPubKeys) > 0 { + sshPubKeys = m.Allocation.SSHPubKeys + } + allocation = &MachineAllocation{ Creator: m.Allocation.Creator, Created: m.Allocation.Created, @@ -560,7 +565,7 @@ func NewMachineResponse(m *metal.Machine, s *metal.Size, p *metal.Partition, i * Image: NewImageResponse(i), Project: m.Allocation.Project, Hostname: m.Allocation.Hostname, - SSHPubKeys: m.Allocation.SSHPubKeys, + SSHPubKeys: sshPubKeys, UserData: m.Allocation.UserData, MachineNetworks: networks, Succeeded: m.Allocation.Succeeded, @@ -585,7 +590,7 @@ func NewMachineResponse(m *metal.Machine, s *metal.Size, p *metal.Partition, i * } } - tags := []string{} + var tags []string if len(m.Tags) > 0 { tags = m.Tags } @@ -637,7 +642,7 @@ func NewMachineResponse(m *metal.Machine, s *metal.Size, p *metal.Partition, i * } func NewMachineRecentProvisioningEvents(ec *metal.ProvisioningEventContainer) *MachineRecentProvisioningEvents { - es := []MachineProvisioningEvent{} + var es []MachineProvisioningEvent if ec == nil { return &MachineRecentProvisioningEvents{ Events: es, diff --git a/cmd/metal-api/internal/service/v1/partition.go b/cmd/metal-api/internal/service/v1/partition.go index beed5d2fc..b1ae03a78 100644 --- a/cmd/metal-api/internal/service/v1/partition.go +++ b/cmd/metal-api/internal/service/v1/partition.go @@ -57,9 +57,9 @@ type ServerCapacity struct { Reservations int `json:"reservations" description:"the amount of reservations for this size"` UsedReservations int `json:"usedreservations" description:"the amount of used reservations for this size"` Faulty int `json:"faulty" description:"servers with issues with this size"` - FaultyMachines []string `json:"faultymachines" description:"servers with issues with this size"` + FaultyMachines []string `json:"faultymachines,omitempty" description:"servers with issues with this size"` Other int `json:"other" description:"servers neither free, allocated or faulty with this size"` - OtherMachines []string `json:"othermachines" description:"servers neither free, allocated or faulty with this size"` + OtherMachines []string `json:"othermachines,omitempty" description:"servers neither free, allocated or faulty with this size"` } func NewPartitionResponse(p *metal.Partition) *PartitionResponse { diff --git a/cmd/metal-api/internal/service/v1/size.go b/cmd/metal-api/internal/service/v1/size.go index 8fe747e1f..679a4451a 100644 --- a/cmd/metal-api/internal/service/v1/size.go +++ b/cmd/metal-api/internal/service/v1/size.go @@ -14,7 +14,7 @@ type SizeReservation struct { Amount int `json:"amount" description:"the amount of reserved machine allocations for this size"` Description string `json:"description,omitempty" description:"a description for this reservation"` ProjectID string `json:"projectid" description:"the project for which this size reservation is considered"` - PartitionIDs []string `json:"partitionids" description:"the partitions in which this size reservation is considered, the amount is valid for every partition"` + PartitionIDs []string `json:"partitionids,omitempty" description:"the partitions in which this size reservation is considered, the amount is valid for every partition"` } type SizeCreateRequest struct { diff --git a/cmd/metal-api/internal/testdata/testdata.go b/cmd/metal-api/internal/testdata/testdata.go index ae90776bc..64b037a41 100644 --- a/cmd/metal-api/internal/testdata/testdata.go +++ b/cmd/metal-api/internal/testdata/testdata.go @@ -26,6 +26,54 @@ import ( // (go tool cover -html=cover.out -o cover.html) // Html output var ( + // Firewalls + FW1 = metal.Machine{ + Base: metal.Base{ID: "1"}, + PartitionID: "1", + SizeID: "1", + Allocation: &metal.MachineAllocation{ + Name: "d1", + ImageID: "image-1", + Project: "p1", + Role: metal.RoleFirewall, + MachineNetworks: []*metal.MachineNetwork{ + { + Private: true, + Vrf: 1, + }, + }, + FirewallRules: &metal.FirewallRules{ + Egress: []metal.EgressRule{ + { + Protocol: metal.ProtocolTCP, + Ports: []int{443}, + To: []string{"0.0.0.0/0"}, + Comment: "test", + }, + }, + Ingress: []metal.IngressRule{}, + }, + }, + Hardware: metal.MachineHardware{ + CPUCores: 8, + Memory: 1 << 30, + Disks: []metal.BlockDevice{ + { + Size: 1000, + }, + { + Size: 1000, + }, + { + Size: 1000, + }, + }, + }, + + IPMI: IPMI1, + Tags: []string{"1"}, + } + // Machines M1 = metal.Machine{ Base: metal.Base{ID: "1"}, diff --git a/go.mod b/go.mod index d4d494354..db22a3d27 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,8 @@ require ( github.com/emicklei/go-restful-openapi/v2 v2.9.1 github.com/emicklei/go-restful/v3 v3.12.0 github.com/go-openapi/spec v0.21.0 + github.com/go-openapi/strfmt v0.23.0 + github.com/go-openapi/validate v0.24.0 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0 @@ -34,14 +36,6 @@ require ( gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.2 ) -replace ( - // netipx and x/exp must be replaced for tailscale < 1.48 - go4.org/netipx => go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 - golang.org/x/exp => golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 - // tailscale 1.48 and headscale 0.22 are not compatible yet - tailscale.com => tailscale.com v1.44.0 -) - require ( connectrpc.com/connect v1.16.0 // indirect dario.cat/mergo v1.0.0 // indirect @@ -77,11 +71,12 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/loads v0.22.0 // indirect github.com/go-openapi/runtime v0.28.0 // indirect - github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gofrs/uuid/v5 v5.0.0 // indirect @@ -208,3 +203,11 @@ require ( nhooyr.io/websocket v1.8.10 // indirect tailscale.com v1.54.0 // indirect ) + +replace ( + // netipx and x/exp must be replaced for tailscale < 1.48 + go4.org/netipx => go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 + golang.org/x/exp => golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 + // tailscale 1.48 and headscale 0.22 are not compatible yet + tailscale.com => tailscale.com v1.44.0 +) diff --git a/spec/metal-api.json b/spec/metal-api.json index 5b8e5969d..f555608bc 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -804,10 +804,7 @@ }, "type": "array" } - }, - "required": [ - "images" - ] + } }, "v1.FilesystemLayoutCreateRequest": { "properties": { @@ -1112,8 +1109,7 @@ "imageid", "partitionid", "projectid", - "sizeid", - "ssh_pub_keys" + "sizeid" ] }, "v1.FirewallEgressRule": { @@ -1145,11 +1141,7 @@ }, "type": "array" } - }, - "required": [ - "ports", - "to" - ] + } }, "v1.FirewallFindRequest": { "properties": { @@ -1369,11 +1361,7 @@ }, "type": "array" } - }, - "required": [ - "from", - "ports" - ] + } }, "v1.FirewallResponse": { "properties": { @@ -1459,8 +1447,7 @@ "id", "ledstate", "liveliness", - "state", - "tags" + "state" ] }, "v1.FirewallRules": { @@ -2087,8 +2074,7 @@ "imageid", "partitionid", "projectid", - "sizeid", - "ssh_pub_keys" + "sizeid" ] }, "v1.MachineAllocation": { @@ -2184,11 +2170,9 @@ "creator", "hostname", "name", - "networks", "project", "reinstall", "role", - "ssh_pub_keys", "succeeded" ] }, @@ -2290,8 +2274,7 @@ "hardware", "ledstate", "liveliness", - "state", - "tags" + "state" ] }, "v1.MachineBlockDevice": { @@ -2599,9 +2582,7 @@ }, "required": [ "cpu_cores", - "disks", - "memory", - "nics" + "memory" ] }, "v1.MachineHardwareBase": { @@ -2626,7 +2607,6 @@ }, "required": [ "cpu_cores", - "disks", "memory" ] }, @@ -2767,8 +2747,7 @@ "ipmi", "ledstate", "liveliness", - "state", - "tags" + "state" ] }, "v1.MachineIpmiReport": { @@ -3160,12 +3139,9 @@ }, "required": [ "asn", - "destinationprefixes", - "ips", "nat", "networkid", "networktype", - "prefixes", "private", "underlay", "vrf" @@ -3196,8 +3172,7 @@ "required": [ "identifier", "mac", - "name", - "neighbors" + "name" ] }, "v1.MachineProvisioningEvent": { @@ -3250,8 +3225,7 @@ }, "required": [ "crash_loop", - "failed_machine_reclaim", - "log" + "failed_machine_reclaim" ] }, "v1.MachineReinstallRequest": { @@ -3362,8 +3336,7 @@ "id", "ledstate", "liveliness", - "state", - "tags" + "state" ] }, "v1.MachineState": { @@ -4391,10 +4364,8 @@ "required": [ "allocated", "faulty", - "faultymachines", "free", "other", - "othermachines", "reservations", "size", "total", @@ -4640,7 +4611,6 @@ }, "required": [ "amount", - "partitionids", "projectid" ] },