mirror of
https://github.com/hanzoai/compute.git
synced 2026-10-30 14:50:41 +00:00
148 lines
5.0 KiB
Go
148 lines
5.0 KiB
Go
// Copyright 2023 Hanzo Industries Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Copyright 2026 Hanzo AI, Inc.
|
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
package controllers
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/hanzoai/compute/logs"
|
|
"github.com/hanzoai/compute/object"
|
|
"github.com/hanzoai/compute/service"
|
|
)
|
|
|
|
// A machine is a machine, wherever it runs.
|
|
//
|
|
// There were two collections. One read a table visor rebuilds from the
|
|
// organization's OWN provider credentials; the other read the cloud API on the
|
|
// house account. A caller wanting "the machines I have" had to ask both and
|
|
// join them — hanzoai/cloud did exactly that, into variables named registry and
|
|
// live, and any caller that asked only one got a partial answer without being
|
|
// told so.
|
|
//
|
|
// They were never two kinds of thing. object.Machine and service.Machine both
|
|
// declare (Owner, Name) as their primary key and both carry Id as an ordinary
|
|
// field holding the PROVIDER'S id. So the identity was always the same, and
|
|
// only the source differed — which is a property of a machine, not a reason for
|
|
// a second address.
|
|
//
|
|
// Source says which. Registry wins a collision, because a machine visor has a
|
|
// row for is one it knows more about than the cloud listing does.
|
|
const (
|
|
sourceRegistry = "registry"
|
|
sourceLive = "live"
|
|
)
|
|
|
|
// machines returns every machine the organization has, from both sources,
|
|
// keyed the one way.
|
|
func (c *ApiController) machines(org string) ([]*object.Machine, error) {
|
|
if _, err := object.SyncMachinesCloud(org); err != nil {
|
|
return nil, err
|
|
}
|
|
rows, err := object.GetMaskedMachines(object.GetMachines(org))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
seen := make(map[string]bool, len(rows))
|
|
for _, m := range rows {
|
|
m.Source = sourceRegistry
|
|
seen[m.Owner+"/"+m.Name] = true
|
|
}
|
|
// The house account is a second source, not a second collection. It is read
|
|
// only when it is configured, and a failure there does not lose the rows
|
|
// that were already found; it is logged, because the answer is then short
|
|
// by every hosted machine the org has.
|
|
if service.ComputeConfigured() {
|
|
live, err := service.ListOrgMachines(org, c.resolveComputeProject(""))
|
|
if err != nil {
|
|
logs.Warning("machines: hosted machines of %s not listed: %v", org, err)
|
|
}
|
|
for _, m := range live {
|
|
if seen[m.Owner+"/"+m.Name] {
|
|
continue
|
|
}
|
|
rows = append(rows, &object.Machine{
|
|
Owner: m.Owner, Name: m.Name, Id: m.Id, Provider: m.Provider,
|
|
Region: m.Region, Zone: m.Zone, Category: m.Category, Type: m.Type,
|
|
Size: m.Size, State: m.State, Tag: m.Tag,
|
|
CreatedTime: m.CreatedTime, ExpireTime: m.ExpireTime,
|
|
DisplayName: m.DisplayName, Image: m.Image, Os: m.Os,
|
|
PublicIp: m.PublicIp, PrivateIp: m.PrivateIp,
|
|
CpuSize: m.CpuSize, MemSize: m.MemSize, Source: sourceLive,
|
|
})
|
|
}
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
// ListMachines answers the organization's machines.
|
|
//
|
|
// @Title ListMachines
|
|
// @Tag Machine API
|
|
// @Description Every machine the organization has, from its own providers and
|
|
// @Description from the house account. Source says which.
|
|
// @Param owner query string false "The organization, for a service caller"
|
|
// @Success 200 {object} Response
|
|
// @router /machines [get]
|
|
func (c *ApiController) ListMachines() {
|
|
org := c.resolveComputeOrg()
|
|
if org == "" {
|
|
c.ResponseError(refuseNoOrg)
|
|
return
|
|
}
|
|
rows, err := c.machines(org)
|
|
if err != nil {
|
|
c.ResponseError(err.Error())
|
|
return
|
|
}
|
|
c.ResponseOk(rows)
|
|
}
|
|
|
|
// GetMachine answers one machine by the key it has always had.
|
|
//
|
|
// @Title GetMachine
|
|
// @Tag Machine API
|
|
// @Description One machine, addressed by the organization that owns it and its name.
|
|
// @Param owner path string true "The organization"
|
|
// @Param name path string true "The machine's name"
|
|
// @Success 200 {object} Response
|
|
// @router /machines/{owner}/{name} [get]
|
|
func (c *ApiController) GetMachine() {
|
|
// The address names WHICH machine; the token names WHOSE. Reading the owner
|
|
// straight off the path would let a caller name any org and be answered from
|
|
// it — resolveComputeOrg runs the same segment through the principal, which
|
|
// is what every other compute read does and the only reason they are scoped.
|
|
org := c.resolveComputeOrg()
|
|
name := strings.TrimSpace(c.Ctx.Param("name"))
|
|
if org == "" || name == "" {
|
|
c.ResponseError(refuseNoOrg)
|
|
return
|
|
}
|
|
rows, err := c.machines(org)
|
|
if err != nil {
|
|
c.ResponseError(err.Error())
|
|
return
|
|
}
|
|
for _, m := range rows {
|
|
if m.Name == name {
|
|
c.ResponseOk(m)
|
|
return
|
|
}
|
|
}
|
|
c.ResponseError("no machine " + name + " in " + org)
|
|
}
|