mirror of
https://github.com/hanzo-fi/stack.git
synced 2026-10-30 20:34:43 +00:00
395 lines
9.7 KiB
Go
395 lines
9.7 KiB
Go
package v1beta1
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"slices"
|
|
|
|
"github.com/formancehq/go-libs/pointer"
|
|
"golang.org/x/mod/semver"
|
|
"k8s.io/apimachinery/pkg/api/equality"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
)
|
|
|
|
// +kubebuilder:object:generate=false
|
|
type EventPublisher interface {
|
|
isEventPublisher()
|
|
}
|
|
|
|
type DevProperties struct {
|
|
// +optional
|
|
// Allow to enable debug mode on the module
|
|
// +kubebuilder:default:=false
|
|
Debug bool `json:"debug"`
|
|
// +optional
|
|
// Allow to enable dev mode on the module
|
|
// Dev mode is used to allow some application to do custom setup in development mode (allow insecure certificates for example)
|
|
// +kubebuilder:default:=false
|
|
Dev bool `json:"dev"`
|
|
}
|
|
|
|
func (p DevProperties) IsDebug() bool {
|
|
return p.Debug
|
|
}
|
|
|
|
func (p DevProperties) IsDev() bool {
|
|
return p.Dev
|
|
}
|
|
|
|
// Condition contains details for one aspect of the current state of this API Resource.
|
|
// ---
|
|
// This struct is intended for direct use as an array at the field path .status.conditions. For example,
|
|
//
|
|
// type FooStatus struct{
|
|
// // Represents the observations of a foo's current state.
|
|
// // Known .status.conditions.type are: "Available", "Progressing", and "Degraded"
|
|
// // +patchMergeKey=type
|
|
// // +patchStrategy=merge
|
|
// // +listType=map
|
|
// // +listMapKey=type
|
|
// Status []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
|
|
//
|
|
// // other fields
|
|
// }
|
|
type Condition struct {
|
|
// type of condition in CamelCase or in foo.example.com/CamelCase.
|
|
// ---
|
|
// Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
|
|
// useful (see .node.status.conditions), the ability to deconflict is important.
|
|
// The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
|
// +required
|
|
// +kubebuilder:validation:Required
|
|
// +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$`
|
|
// +kubebuilder:validation:MaxLength=316
|
|
Type string `json:"type" protobuf:"bytes,1,opt,name=type"`
|
|
// status of the condition, one of True, False, Unknown.
|
|
// +required
|
|
// +kubebuilder:validation:Required
|
|
// +kubebuilder:validation:Enum=True;False;Unknown
|
|
Status metav1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status"`
|
|
// observedGeneration represents the .metadata.generation that the condition was set based upon.
|
|
// For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
|
// with respect to the current state of the instance.
|
|
// +optional
|
|
// +kubebuilder:validation:Minimum=0
|
|
ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"`
|
|
// lastTransitionTime is the last time the condition transitioned from one status to another.
|
|
// This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
|
// +required
|
|
// +kubebuilder:validation:Required
|
|
// +kubebuilder:validation:Type=string
|
|
// +kubebuilder:validation:Format=date-time
|
|
LastTransitionTime metav1.Time `json:"lastTransitionTime" protobuf:"bytes,4,opt,name=lastTransitionTime"`
|
|
// message is a human readable message indicating details about the transition.
|
|
// This may be an empty string.
|
|
// +kubebuilder:validation:Required
|
|
// +kubebuilder:validation:MaxLength=32768
|
|
Message string `json:"message" protobuf:"bytes,6,opt,name=message"`
|
|
// reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
|
// Producers of specific condition types may define expected values and meanings for this field,
|
|
// and whether the values are considered a guaranteed API.
|
|
// The value should be a CamelCase string.
|
|
// This field may not be empty.
|
|
// +optional
|
|
// +kubebuilder:validation:MaxLength=1024
|
|
// +kubebuilder:validation:Pattern=`^([A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?)?$`
|
|
Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"`
|
|
}
|
|
|
|
func (in *Condition) SetStatus(v metav1.ConditionStatus) *Condition {
|
|
in.Status = v
|
|
|
|
return in
|
|
}
|
|
|
|
func (in *Condition) SetMessage(v string) *Condition {
|
|
in.Message = v
|
|
|
|
return in
|
|
}
|
|
|
|
func (in *Condition) SetReason(reason string) *Condition {
|
|
in.Reason = reason
|
|
|
|
return in
|
|
}
|
|
|
|
func (in *Condition) Fail(v string) *Condition {
|
|
in.SetStatus(metav1.ConditionFalse)
|
|
in.SetMessage(v)
|
|
|
|
return in
|
|
}
|
|
|
|
func NewCondition(t string, generation int64) *Condition {
|
|
return &Condition{
|
|
Type: t,
|
|
Status: metav1.ConditionTrue,
|
|
ObservedGeneration: generation,
|
|
LastTransitionTime: metav1.Now(),
|
|
}
|
|
}
|
|
|
|
type Conditions []Condition
|
|
|
|
func (c *Conditions) Delete(p ConditionPredicate) *Conditions {
|
|
for i, existingCondition := range *c {
|
|
if p(existingCondition) {
|
|
if i < len(*c)-1 {
|
|
*c = append((*c)[:i], (*c)[i+1:]...)
|
|
} else {
|
|
*c = (*c)[:i]
|
|
}
|
|
return c
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (c *Conditions) AppendOrReplace(newCondition Condition, p ConditionPredicate) *Condition {
|
|
c.Delete(p)
|
|
*c = append(*c, newCondition)
|
|
slices.SortStableFunc(*c, func(a, b Condition) int {
|
|
switch {
|
|
case a.Type < b.Type:
|
|
return -1
|
|
case a.Type > b.Type:
|
|
return 1
|
|
default:
|
|
switch {
|
|
case a.Reason < b.Reason:
|
|
return -1
|
|
case a.Reason > b.Reason:
|
|
return 1
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
})
|
|
return &newCondition
|
|
}
|
|
|
|
func (c *Conditions) Get(conditionType string) *Condition {
|
|
for _, condition := range *c {
|
|
if condition.Type == conditionType {
|
|
return &condition
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Conditions) Check(p ConditionPredicate) bool {
|
|
for _, condition := range *c {
|
|
if condition.Status == metav1.ConditionTrue && p(condition) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// +kubebuilder:object:generate=false
|
|
type ConditionPredicate func(condition Condition) bool
|
|
|
|
func AndConditions(predicates ...ConditionPredicate) ConditionPredicate {
|
|
return func(condition Condition) bool {
|
|
for _, predicate := range predicates {
|
|
if !predicate(condition) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
func ConditionTypeMatch(t string) ConditionPredicate {
|
|
return func(condition Condition) bool {
|
|
return condition.Type == t
|
|
}
|
|
}
|
|
|
|
func ConditionReasonMatch(reason string) ConditionPredicate {
|
|
return func(condition Condition) bool {
|
|
return condition.Reason == reason
|
|
}
|
|
}
|
|
|
|
func ConditionGenerationMatch(generation int64) ConditionPredicate {
|
|
return func(condition Condition) bool {
|
|
return condition.ObservedGeneration == generation
|
|
}
|
|
}
|
|
|
|
type Status struct {
|
|
//+optional
|
|
// Ready indicates if the resource is seen as completely reconciled
|
|
Ready bool `json:"ready"`
|
|
//+optional
|
|
// Info can contain any additional like reconciliation errors
|
|
Info string `json:"info,omitempty"`
|
|
//+optional
|
|
Conditions Conditions `json:"conditions,omitempty"`
|
|
}
|
|
|
|
func (c *Status) SetReady(ready bool) {
|
|
c.Ready = ready
|
|
}
|
|
|
|
func (c *Status) SetError(err string) {
|
|
c.Info = err
|
|
}
|
|
|
|
type AuthConfig struct {
|
|
// +optional
|
|
ReadKeySetMaxRetries int `json:"readKeySetMaxRetries"`
|
|
// +optional
|
|
CheckScopes bool `json:"checkScopes"`
|
|
}
|
|
|
|
// +kubebuilder:object:generate=false
|
|
type Module interface {
|
|
Dependent
|
|
GetVersion() string
|
|
IsDebug() bool
|
|
IsDev() bool
|
|
IsEE() bool
|
|
}
|
|
|
|
type ModuleProperties struct {
|
|
DevProperties `json:",inline"`
|
|
//+optional
|
|
// Version allow to override global version defined at stack level for a specific module
|
|
Version string `json:"version,omitempty"`
|
|
}
|
|
|
|
func (in *ModuleProperties) CompareVersion(stack *Stack, version string) int {
|
|
actualVersion := in.Version
|
|
if actualVersion == "" {
|
|
actualVersion = stack.Spec.Version
|
|
}
|
|
if !semver.IsValid(actualVersion) {
|
|
return 1
|
|
}
|
|
|
|
return semver.Compare(actualVersion, version)
|
|
}
|
|
|
|
// +kubebuilder:object:generate=false
|
|
type Dependent interface {
|
|
Object
|
|
GetStack() string
|
|
}
|
|
|
|
type StackDependency struct {
|
|
// Stack indicates the stack on which the module is installed
|
|
Stack string `json:"stack,omitempty" yaml:"-"`
|
|
}
|
|
|
|
func (d StackDependency) GetStack() string {
|
|
return d.Stack
|
|
}
|
|
|
|
// +kubebuilder:object:generate=false
|
|
type Object interface {
|
|
client.Object
|
|
SetReady(bool)
|
|
IsReady() bool
|
|
SetError(string)
|
|
GetConditions() *Conditions
|
|
}
|
|
|
|
// +kubebuilder:object:generate=false
|
|
type Resource interface {
|
|
Dependent
|
|
isResource()
|
|
}
|
|
|
|
// +k8s:openapi-gen=true
|
|
// +kubebuilder:validation:Type=string
|
|
type URI struct {
|
|
*url.URL `json:"-"`
|
|
}
|
|
|
|
func (u URI) String() string {
|
|
if u.URL == nil {
|
|
return "nil"
|
|
}
|
|
return u.URL.String()
|
|
}
|
|
|
|
func (u URI) IsZero() bool {
|
|
return u.URL == nil
|
|
}
|
|
|
|
func (u *URI) DeepCopyInto(v *URI) {
|
|
cp := *u.URL
|
|
if u.User != nil {
|
|
cp.User = pointer.For(*u.User)
|
|
}
|
|
v.URL = pointer.For(cp)
|
|
}
|
|
|
|
func (u *URI) MarshalJSON() ([]byte, error) {
|
|
return []byte(fmt.Sprintf(`"%s"`, u.String())), nil
|
|
}
|
|
|
|
func (u *URI) UnmarshalJSON(data []byte) error {
|
|
var s string
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return nil
|
|
}
|
|
|
|
v, err := url.Parse(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
*u = URI{
|
|
URL: v,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (in *URI) WithoutQuery() *URI {
|
|
cp := *in.URL
|
|
cp.ForceQuery = false
|
|
cp.RawQuery = ""
|
|
return &URI{
|
|
URL: &cp,
|
|
}
|
|
}
|
|
|
|
func ParseURL(v string) (*URI, error) {
|
|
ret, err := url.Parse(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &URI{
|
|
URL: ret,
|
|
}, nil
|
|
}
|
|
|
|
func init() {
|
|
if err := equality.Semantic.AddFunc(func(a, b *URI) bool {
|
|
if a == nil && b != nil {
|
|
return false
|
|
}
|
|
if a != nil && b == nil {
|
|
return false
|
|
}
|
|
if a == nil && b == nil {
|
|
return true
|
|
}
|
|
return a.String() == b.String()
|
|
}); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
const (
|
|
StackLabel = "formance.com/stack"
|
|
SkipLabel = "formance.com/skip"
|
|
CreatedByAgentLabel = "formance.com/created-by-agent"
|
|
)
|