Skip to content

Commit

Permalink
Merge POC into consul-k8s
Browse files Browse the repository at this point in the history
Signed-off-by: Ashwin Venkatesh <ashwin@hashicorp.com>
  • Loading branch information
Ashwin Venkatesh committed Aug 13, 2020
1 parent 85c7713 commit ee44718
Show file tree
Hide file tree
Showing 46 changed files with 2,531 additions and 8 deletions.
36 changes: 36 additions & 0 deletions api/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
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.
*/

// Package v1alpha1 contains API Schema definitions for the consul.hashicorp.com v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=consul.hashicorp.com
package v1alpha1

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: "consul.hashicorp.com", Version: "v1alpha1"}

// 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
)
116 changes: 116 additions & 0 deletions api/v1alpha1/servicedefaults_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
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.
*/

package v1alpha1

import (
consulapi "github.com/hashicorp/consul/api"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// ServiceDefaultsSpec defines the desired state of ServiceDefaults
type ServiceDefaultsSpec struct {
Protocol string `json:"protocol,omitempty"`
MeshGateway MeshGatewayConfig `json:"meshGateway,omitempty"`
Expose ExposeConfig `json:"expose,omitempty"`
ExternalSNI string `json:"externalSNI,omitempty"`
}

// ServiceDefaultsStatus defines the observed state of ServiceDefaults
type ServiceDefaultsStatus struct {
Status `json:",inline"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

// ServiceDefaults is the Schema for the servicedefaults API
// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul"
type ServiceDefaults struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ServiceDefaultsSpec `json:"spec,omitempty"`
Status ServiceDefaultsStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

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

func init() {
SchemeBuilder.Register(&ServiceDefaults{}, &ServiceDefaultsList{})
}

func (s *ServiceDefaults) ToConsul() *consulapi.ServiceConfigEntry {
return &consulapi.ServiceConfigEntry{
Kind: consulapi.ServiceDefaults,
Name: s.Name,
//Namespace: s.Namespace, // todo: don't set this unless enterprise
Protocol: s.Spec.Protocol,
MeshGateway: consulapi.MeshGatewayConfig{
Mode: consulapi.MeshGatewayModeDefault, //this will change. forcing it to default for now.
},
Expose: consulapi.ExposeConfig{
Checks: s.Spec.Expose.Checks,
Paths: []consulapi.ExposePath{}, //will create a helper on our expose paths to translate to consul expose paths
},
ExternalSNI: s.Spec.ExternalSNI,
}
}

// this will check if the consul struct shares the same spec as the spec of the resource
func (s *ServiceDefaults) MatchesConsul(entry *consulapi.ServiceConfigEntry) bool {
matches := s.Name == entry.GetName() &&
s.Spec.Protocol == entry.Protocol &&
s.Spec.MeshGateway.Mode == string(entry.MeshGateway.Mode) &&
s.Spec.Expose.Checks == entry.Expose.Checks &&
s.Spec.ExternalSNI == entry.ExternalSNI
if !matches {
return false
}

// Also check each exposed path config.
if len(s.Spec.Expose.Paths) != len(entry.Expose.Paths) {
return false
}
for _, path := range s.Spec.Expose.Paths {
found := false
for _, entryPath := range entry.Expose.Paths {
if path.ParsedFromCheck == entryPath.ParsedFromCheck &&
path.Protocol == entryPath.Protocol &&
path.Path == entryPath.Path &&
path.ListenerPort == entryPath.ListenerPort &&
path.LocalPathPort == entryPath.LocalPathPort {
found = true
break
}
}

if !found {
return false
}
}
return true
}
92 changes: 92 additions & 0 deletions api/v1alpha1/servicedefaults_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
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.
*/

package v1alpha1

import (
"context"
"fmt"

"github.com/hashicorp/consul/api"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

// log is for logging in this package.
var servicedefaultslog = logf.Log.WithName("servicedefaults-resource")

// todo: use our own validating webhook so we can inject this properly
var ConsulClient *api.Client
var KubeClient client.Client

func (r *ServiceDefaults) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!

// +kubebuilder:webhook:path=/mutate-consul-hashicorp-com-v1alpha1-servicedefaults,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=servicedefaults,verbs=create;update,versions=v1alpha1,name=mservicedefaults.kb.io

var _ webhook.Defaulter = &ServiceDefaults{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *ServiceDefaults) Default() {
servicedefaultslog.Info("default", "name", r.Name)

// TODO(user): fill in your defaulting logic.
}

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
// +kubebuilder:webhook:verbs=create;update,path=/validate-consul-hashicorp-com-v1alpha1-servicedefaults,mutating=false,failurePolicy=fail,groups=consul.hashicorp.com,resources=servicedefaults,versions=v1alpha1,name=vservicedefaults.kb.io

var _ webhook.Validator = &ServiceDefaults{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *ServiceDefaults) ValidateCreate() error {
servicedefaultslog.Info("validate create", "name", r.Name)
var svcDefaultsList ServiceDefaultsList
if err := KubeClient.List(context.Background(), &svcDefaultsList); err != nil {
return err
}
for _, item := range svcDefaultsList.Items {
if item.Name == r.Name {
return fmt.Errorf("ServiceDefaults resource with name %q is already defined in namespace %q – all ServiceDefaults resources must have unique names across namespaces",
r.Name, item.Namespace)
}
}
return nil
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *ServiceDefaults) ValidateUpdate(old runtime.Object) error {
servicedefaultslog.Info("validate update", "name", r.Name)

// TODO(user): fill in your validation logic upon object update.
return nil
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *ServiceDefaults) ValidateDelete() error {
servicedefaultslog.Info("validate delete", "name", r.Name)

// TODO(user): fill in your validation logic upon object deletion.
return nil
}
89 changes: 89 additions & 0 deletions api/v1alpha1/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package v1alpha1

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

// Conditions is the schema for the conditions portion of the payload
type Conditions []Condition

// ConditionType is a camel-cased condition type.
type ConditionType string

const (
// ConditionSynced specifies that the resource has been synced with Consul.
ConditionSynced ConditionType = "Synced"
)

// Conditions define a readiness condition for a Consul resource.
// See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
// +k8s:deepcopy-gen=true
// +k8s:openapi-gen=true
type Condition struct {
// Type of condition.
// +required
Type ConditionType `json:"type" description:"type of status condition"`

// Status of the condition, one of True, False, Unknown.
// +required
Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"`

// LastTransitionTime is the last time the condition transitioned from one status to another.
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" description:"last time the condition transitioned from one status to another"`

// The reason for the condition's last transition.
// +optional
Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"`

// A human readable message indicating details about the transition.
// +optional
Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"`
}

// IsTrue is true if the condition is True
func (c *Condition) IsTrue() bool {
if c == nil {
return false
}
return c.Status == corev1.ConditionTrue
}

// IsFalse is true if the condition is False
func (c *Condition) IsFalse() bool {
if c == nil {
return false
}
return c.Status == corev1.ConditionFalse
}

// IsUnknown is true if the condition is Unknown
func (c *Condition) IsUnknown() bool {
if c == nil {
return true
}
return c.Status == corev1.ConditionUnknown
}

// Status shows how we expect folks to embed Conditions in
// their Status field.
// WARNING: Adding fields to this struct will add them to all Consul-k8s resources.
// +k8s:deepcopy-gen=true
// +k8s:openapi-gen=true
type Status struct {
// Conditions indicate the latest available observations of a resource's current state.
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (s *Status) GetCondition(t ConditionType) *Condition {
for _, cond := range s.Conditions {
if cond.Type == t {
return &cond
}
}
return nil
}
58 changes: 58 additions & 0 deletions api/v1alpha1/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package v1alpha1

type MeshGatewayMode string

const (
// MeshGatewayModeDefault represents no specific mode and should
// be used to indicate that a different layer of the configuration
// chain should take precedence
MeshGatewayModeDefault MeshGatewayMode = ""

// MeshGatewayModeNone represents that the Upstream Connect connections
// should be direct and not flow through a mesh gateway.
MeshGatewayModeNone MeshGatewayMode = "none"

// MeshGatewayModeLocal represents that the Upstrea Connect connections
// should be made to a mesh gateway in the local datacenter. This is
MeshGatewayModeLocal MeshGatewayMode = "local"

// MeshGatewayModeRemote represents that the Upstream Connect connections
// should be made to a mesh gateway in a remote datacenter.
MeshGatewayModeRemote MeshGatewayMode = "remote"
)

// MeshGatewayConfig controls how Mesh Gateways are used for upstream Connect
// services
type MeshGatewayConfig struct {
// Mode is the mode that should be used for the upstream connection.
Mode string `json:"mode,omitempty"`
}

// ExposeConfig describes HTTP paths to expose through Envoy outside of Connect.
// Users can expose individual paths and/or all HTTP/GRPC paths for checks.
type ExposeConfig struct {
// Checks defines whether paths associated with Consul checks will be exposed.
// This flag triggers exposing all HTTP and GRPC check paths registered for the service.
Checks bool `json:"checks,omitempty"`

// Paths is the list of paths exposed through the proxy.
Paths []ExposePath `json:"paths,omitempty"`
}

type ExposePath struct {
// ListenerPort defines the port of the proxy's listener for exposed paths.
ListenerPort int `json:"listenerPort,omitempty"`

// Path is the path to expose through the proxy, ie. "/metrics."
Path string `json:"path,omitempty"`

// LocalPathPort is the port that the service is listening on for the given path.
LocalPathPort int `json:"localPathPort,omitempty"`

// Protocol describes the upstream's service protocol.
// Valid values are "http" and "http2", defaults to "http"
Protocol string `json:"protocol,omitempty"`

// ParsedFromCheck is set if this path was parsed from a registered check
ParsedFromCheck bool `json:"parsedFromCheck,omitempty"`
}
Loading

0 comments on commit ee44718

Please sign in to comment.