Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions api/v1alpha2/common_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: AGPL-3.0-only

package v1alpha2

// NodeReference is a reference to a graph Node by name. Both Edge endpoints
// and any future node-to-node pointers use this type.
type NodeReference struct {
// Name of the referenced Node.
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
}

// AttributeValueType enumerates the scalar value kinds an attribute may hold.
// Attribute values are always stored as strings on the wire; the type drives
// how the admission webhook parses and validates them.
//
// +kubebuilder:validation:Enum=String;Integer;Float;Boolean
type AttributeValueType string

const (
// AttributeString is an arbitrary UTF-8 string.
AttributeString AttributeValueType = "String"
// AttributeInteger is a base-10 signed integer.
AttributeInteger AttributeValueType = "Integer"
// AttributeFloat is a decimal floating-point number.
AttributeFloat AttributeValueType = "Float"
// AttributeBoolean is "true" or "false".
AttributeBoolean AttributeValueType = "Boolean"
)

// AttributeSchema describes one allowed attribute key on a Node or Edge type.
// The set of AttributeSchemas on a NodeType/EdgeType is the closed schema the
// admission webhook validates each object's attributes against.
type AttributeSchema struct {
// Key is the attribute name (the map key in spec.attributes).
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Key string `json:"key"`

// Type is the scalar kind the value must parse as.
//
// +kubebuilder:validation:Required
Type AttributeValueType `json:"type"`

// Required marks the attribute as mandatory. Objects missing a required
// attribute are rejected at admission.
//
// +optional
Required bool `json:"required,omitempty"`

// Enum optionally restricts a String attribute to a fixed set of values.
//
// +optional
// +listType=atomic
Enum []string `json:"enum,omitempty"`
}
107 changes: 107 additions & 0 deletions api/v1alpha2/edge_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: AGPL-3.0-only

package v1alpha2

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EdgeSpec defines the desired state of a graph Edge.
//
// +kubebuilder:validation:XValidation:rule="self.type == oldSelf.type",message="type is immutable"
// +kubebuilder:validation:XValidation:rule="self.from.name != self.to.name",message="edge endpoints must be distinct"
type EdgeSpec struct {
// Type names the relationship class. The value must match the name of an
// existing EdgeType, which constrains the endpoint node types and the
// attributes this edge may carry. Typical values: located-in, member-of,
// mounted-in, connects, realized-by, provided-by. This field is immutable.
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Type string `json:"type"`

// From is the source endpoint Node.
//
// +kubebuilder:validation:Required
From NodeReference `json:"from"`

// To is the target endpoint Node.
//
// +kubebuilder:validation:Required
To NodeReference `json:"to"`

// Attributes is the edge's key/value attribute bag. The admission webhook
// validates these against the matching EdgeType's attribute schema.
//
// +optional
Attributes map[string]string `json:"attributes,omitempty"`
}

// EdgeStatus defines the observed state of a graph Edge.
type EdgeStatus struct {
// Represents the observations of an edge's current state.
// Known condition types are: "Ready", "EndpointsResolved".
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
}

const (
// EdgeReady is the condition type indicating the Edge has been accepted
// and both endpoints resolve.
EdgeReady = "Ready"

// EdgeEndpointsResolved is set once the controller has verified both
// endpoint Nodes exist. Reported alongside Ready for observability.
EdgeEndpointsResolved = "EndpointsResolved"
)

const (
// EdgeReadyReason indicates the Edge is accepted and ready for use.
EdgeReadyReason = "Accepted"

// EdgePendingReason indicates the Edge has not yet been reconciled.
EdgePendingReason = "Pending"

// EdgeTypeNotFoundReason indicates the referenced EdgeType does not exist.
EdgeTypeNotFoundReason = "EdgeTypeNotFound"

// EdgeEndpointNotFoundReason indicates an endpoint Node does not exist.
EdgeEndpointNotFoundReason = "EndpointNotFound"
)

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type"
// +kubebuilder:printcolumn:name="From",type="string",JSONPath=".spec.from.name"
// +kubebuilder:printcolumn:name="To",type="string",JSONPath=".spec.to.name"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:printcolumn:name="Reason",type="string",JSONPath=`.status.conditions[?(@.type=="Ready")].reason`

// Edge is a directed relationship between two graph Nodes. Its relationship
// class is given by spec.type and its descriptive data by spec.attributes. It
// supersedes the v1alpha1 Link/Cable/Circuit kinds and the inline parent refs.
// See RFC milo-os/inventory#43.
type Edge struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// +kubebuilder:validation:Required
Spec EdgeSpec `json:"spec,omitempty"`

// +kubebuilder:default={conditions:{{type:"Ready",status:"False",reason:"Pending",message:"Waiting for reconciliation",lastTransitionTime:"1970-01-01T00:00:00Z"}}}
Status EdgeStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// EdgeList contains a list of Edge.
type EdgeList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Edge `json:"items"`
}

func init() {
SchemeBuilder.Register(&Edge{}, &EdgeList{})
}
94 changes: 94 additions & 0 deletions api/v1alpha2/edgetype_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: AGPL-3.0-only

package v1alpha2

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EndpointConstraint restricts which NodeTypes an Edge's endpoints may point
// at. An empty list on either side means "any node type".
type EndpointConstraint struct {
// FromTypes is the set of allowed NodeType names for the edge's `from`
// endpoint. Empty means any.
//
// +optional
// +listType=set
FromTypes []string `json:"fromTypes,omitempty"`

// ToTypes is the set of allowed NodeType names for the edge's `to`
// endpoint. Empty means any.
//
// +optional
// +listType=set
ToTypes []string `json:"toTypes,omitempty"`
}

// EdgeTypeSpec describes the closed attribute schema and endpoint constraints
// for Edges whose spec.type equals this EdgeType's metadata.name.
type EdgeTypeSpec struct {
// DisplayName is a human-readable label for the edge type.
//
// +optional
DisplayName string `json:"displayName,omitempty"`

// Endpoints constrains the NodeTypes the edge may connect.
//
// +optional
Endpoints EndpointConstraint `json:"endpoints,omitempty"`

// Attributes is the closed set of attribute keys an Edge of this type may
// carry.
//
// +optional
// +listType=map
// +listMapKey=key
Attributes []AttributeSchema `json:"attributes,omitempty"`
}

// EdgeTypeStatus defines the observed state of an EdgeType.
type EdgeTypeStatus struct {
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
}

const (
// EdgeTypeReady indicates the EdgeType has been accepted.
EdgeTypeReady = "Ready"
// EdgeTypeReadyReason indicates the EdgeType is accepted and ready.
EdgeTypeReadyReason = "Accepted"
// EdgeTypePendingReason indicates the EdgeType has not yet reconciled.
EdgeTypePendingReason = "Pending"
)

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:printcolumn:name="Display",type="string",JSONPath=".spec.displayName"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type=="Ready")].status`

// EdgeType describes the attribute schema and endpoint constraints for a
// class of graph Edges. See RFC milo-os/inventory#43.
type EdgeType struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// +kubebuilder:validation:Required
Spec EdgeTypeSpec `json:"spec,omitempty"`

// +kubebuilder:default={conditions:{{type:"Ready",status:"False",reason:"Pending",message:"Waiting for reconciliation",lastTransitionTime:"1970-01-01T00:00:00Z"}}}
Status EdgeTypeStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// EdgeTypeList contains a list of EdgeType.
type EdgeTypeList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []EdgeType `json:"items"`
}

func init() {
SchemeBuilder.Register(&EdgeType{}, &EdgeTypeList{})
}
33 changes: 33 additions & 0 deletions api/v1alpha2/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: AGPL-3.0-only

// Package v1alpha2 contains API Schema definitions for the property-graph
// model of the inventory.miloapis.com API group. It replaces the per-kind
// v1alpha1 hierarchy with two generic kinds -- Node (graph vertex) and Edge
// (graph relationship) -- whose shape is described by the NodeType and
// EdgeType schema-registry kinds. See RFC milo-os/inventory#43.
//
// The prototype uses the group graph.inventory.miloapis.com so it installs
// side-by-side with the v1alpha1 hierarchy (whose `Node` kind would otherwise
// collide at the CRD level -- the very collision RFC #43 resolves). The
// production migration reclaims the inventory.miloapis.com group once
// v1alpha1 is removed.
//
// +kubebuilder:object:generate=true
// +groupName=graph.inventory.miloapis.com
package v1alpha2

import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)

var (
// GroupVersion is group version used to register these objects.
GroupVersion = schema.GroupVersion{Group: "graph.inventory.miloapis.com", Version: "v1alpha2"}

// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
87 changes: 87 additions & 0 deletions api/v1alpha2/node_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: AGPL-3.0-only

package v1alpha2

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// NodeSpec defines the desired state of a graph Node.
//
// +kubebuilder:validation:XValidation:rule="self.type == oldSelf.type",message="type is immutable"
type NodeSpec struct {
// Type names the node's asset class. The value must match the name of an
// existing NodeType, which describes the attributes this node may carry.
// Typical values are the former v1alpha1 kinds: Region, Site, Cluster,
// NetworkDevice, Rack, Provider, Circuit, Cable, Port, VirtualMachine,
// Host (the former compute Node), Fleet. This field is immutable.
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Type string `json:"type"`

// Attributes is the node's key/value attribute bag. The admission webhook
// validates these against the matching NodeType's attribute schema.
//
// +optional
Attributes map[string]string `json:"attributes,omitempty"`
}

// NodeStatus defines the observed state of a graph Node.
type NodeStatus struct {
// Represents the observations of a node's current state.
// Known condition types are: "Ready".
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
}

const (
// NodeReady is the condition type indicating the Node has been accepted
// and validated against its NodeType.
NodeReady = "Ready"
)

const (
// NodeReadyReason indicates the Node is accepted and ready for use.
NodeReadyReason = "Accepted"

// NodePendingReason indicates the Node has not yet been reconciled.
NodePendingReason = "Pending"

// NodeTypeNotFoundReason indicates the referenced NodeType does not exist.
NodeTypeNotFoundReason = "NodeTypeNotFound"
)

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster,shortName=invnode
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:printcolumn:name="Reason",type="string",JSONPath=`.status.conditions[?(@.type=="Ready")].reason`

// Node is a vertex in the inventory property graph. Its asset class is given
// by spec.type and its descriptive data by spec.attributes. It supersedes the
// per-kind v1alpha1 inventory kinds. See RFC milo-os/inventory#43.
type Node struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// +kubebuilder:validation:Required
Spec NodeSpec `json:"spec,omitempty"`

// +kubebuilder:default={conditions:{{type:"Ready",status:"False",reason:"Pending",message:"Waiting for reconciliation",lastTransitionTime:"1970-01-01T00:00:00Z"}}}
Status NodeStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// NodeList contains a list of Node.
type NodeList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Node `json:"items"`
}

func init() {
SchemeBuilder.Register(&Node{}, &NodeList{})
}
Loading