Add Zone and Designate CRs

Change-Id: I7b59fc5ae66adb4d123c10249e2321c6bc6537c5
This commit is contained in:
okozachenko 2020-04-04 23:59:37 -07:00
parent 5acd3683ad
commit 1d83657ec1
25 changed files with 1566 additions and 22 deletions

View File

@ -0,0 +1,39 @@
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// DesignateSpec defines the desired state of Designate
type DesignateSpec struct {
Credentials string `json:"credentials"`
CloudName string `json:"cloudname"`
}
// DesignateStatus defines the observed state of Designate
type DesignateStatus struct {
}
// +kubebuilder:object:root=true
// Designate is the Schema for the Designates API
type Designate struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec DesignateSpec `json:"spec,omitempty"`
Status DesignateStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// DesignateList contains a list of Designate
type DesignateList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Designate `json:"items"`
}
func init() {
SchemeBuilder.Register(&Designate{}, &DesignateList{})
}

View File

@ -0,0 +1,20 @@
// Package v1 contains API Schema definitions for the infrastructure v1 API group
// +kubebuilder:object:generate=true
// +groupName=dns.openstack.org
package v1
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: "dns.openstack.org", Version: "v1"}
// 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
)

44
api/dns/v1/zone_type.go Normal file
View File

@ -0,0 +1,44 @@
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ZoneSpec defines the desired state of Zone
type ZoneSpec struct {
Domain string `json:"domain"`
TTL int `json:"ttl"`
Email string `json:"email"`
Description string `json:"description,omitempty"`
Type string `json:"type,omitempty"`
}
// ZoneStatus defines the observed state of Zone
type ZoneStatus struct {
// +kubebuilder:validation:Default=Pending
ZoneID string `json:"zoneId"`
}
// +kubebuilder:object:root=true
// Zone is the Schema for the Zones API
type Zone struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ZoneSpec `json:"spec,omitempty"`
Status ZoneStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// ZoneList contains a list of Zone
type ZoneList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Zone `json:"items"`
}
func init() {
SchemeBuilder.Register(&Zone{}, &ZoneList{})
}

View File

@ -0,0 +1,203 @@
// +build !ignore_autogenerated
/*
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.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Designate) DeepCopyInto(out *Designate) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Designate.
func (in *Designate) DeepCopy() *Designate {
if in == nil {
return nil
}
out := new(Designate)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Designate) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DesignateList) DeepCopyInto(out *DesignateList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Designate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DesignateList.
func (in *DesignateList) DeepCopy() *DesignateList {
if in == nil {
return nil
}
out := new(DesignateList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *DesignateList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DesignateSpec) DeepCopyInto(out *DesignateSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DesignateSpec.
func (in *DesignateSpec) DeepCopy() *DesignateSpec {
if in == nil {
return nil
}
out := new(DesignateSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DesignateStatus) DeepCopyInto(out *DesignateStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DesignateStatus.
func (in *DesignateStatus) DeepCopy() *DesignateStatus {
if in == nil {
return nil
}
out := new(DesignateStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Zone) DeepCopyInto(out *Zone) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Zone.
func (in *Zone) DeepCopy() *Zone {
if in == nil {
return nil
}
out := new(Zone)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Zone) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ZoneList) DeepCopyInto(out *ZoneList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Zone, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZoneList.
func (in *ZoneList) DeepCopy() *ZoneList {
if in == nil {
return nil
}
out := new(ZoneList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ZoneList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ZoneSpec) DeepCopyInto(out *ZoneSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZoneSpec.
func (in *ZoneSpec) DeepCopy() *ZoneSpec {
if in == nil {
return nil
}
out := new(ZoneSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ZoneStatus) DeepCopyInto(out *ZoneStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZoneStatus.
func (in *ZoneStatus) DeepCopy() *ZoneStatus {
if in == nil {
return nil
}
out := new(ZoneStatus)
in.DeepCopyInto(out)
return out
}

61
builders/zone.go Normal file
View File

@ -0,0 +1,61 @@
package builders
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
dnsv1 "opendev.org/vexxhost/openstack-operator/api/dns/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
// ZoneBuilder defines the interface to build a Zone
type ZoneBuilder struct {
obj *dnsv1.Zone
owner metav1.Object
scheme *runtime.Scheme
}
// Zone returns a new service builder
func Zone(existing *dnsv1.Zone, owner metav1.Object, scheme *runtime.Scheme) *ZoneBuilder {
existing.Annotations = map[string]string{}
return &ZoneBuilder{
obj: existing,
owner: owner,
scheme: scheme,
}
}
// Annotation sets one set annotation
func (z *ZoneBuilder) Annotation(key, value string) *ZoneBuilder {
z.obj.Annotations[key] = value
return z
}
// Labels specifies labels for the Zone
func (z *ZoneBuilder) Labels(labels map[string]string) *ZoneBuilder {
z.obj.ObjectMeta.Labels = labels
return z
}
// Domain sets Domain for the Zone
func (z *ZoneBuilder) Domain(domain string) *ZoneBuilder {
z.obj.Spec.Domain = domain
return z
}
// TTL sets TTL for the Zone
func (z *ZoneBuilder) TTL(ttl int) *ZoneBuilder {
z.obj.Spec.TTL = ttl
return z
}
// Email sets TTL for the Email
func (z *ZoneBuilder) Email(email string) *ZoneBuilder {
z.obj.Spec.Email = email
return z
}
// Build returns a complete Zone object
func (z *ZoneBuilder) Build() error {
return controllerutil.SetControllerReference(z.owner, z.obj, z.scheme)
}

View File

@ -0,0 +1,59 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: designates.dns.openstack.org
spec:
group: dns.openstack.org
names:
kind: Designate
listKind: DesignateList
plural: designates
singular: designate
scope: Namespaced
validation:
openAPIV3Schema:
description: Designate is the Schema for the Designates API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: DesignateSpec defines the desired state of Designate
properties:
cloudname:
type: string
credentials:
type: string
required:
- cloudname
- credentials
type: object
status:
description: DesignateStatus defines the observed state of Designate
type: object
type: object
version: v1
versions:
- name: v1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -0,0 +1,71 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: zones.dns.openstack.org
spec:
group: dns.openstack.org
names:
kind: Zone
listKind: ZoneList
plural: zones
singular: zone
scope: Namespaced
validation:
openAPIV3Schema:
description: Zone is the Schema for the Zones API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ZoneSpec defines the desired state of Zone
properties:
description:
type: string
domain:
type: string
email:
type: string
ttl:
type: integer
type:
type: string
required:
- domain
- email
- ttl
type: object
status:
description: ZoneStatus defines the observed state of Zone
properties:
zoneId:
type: string
required:
- zoneId
type: object
type: object
version: v1
versions:
- name: v1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -43,6 +43,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
@ -56,6 +68,46 @@ rules:
- patch
- update
- watch
- apiGroups:
- dns.openstack.org
resources:
- designates
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- dns.openstack.org
resources:
- designates/status
verbs:
- get
- patch
- update
- apiGroups:
- dns.openstack.org
resources:
- zones
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- dns.openstack.org
resources:
- zones/status
verbs:
- get
- patch
- update
- apiGroups:
- infrastructure.vexxhost.cloud
resources:

View File

@ -6,4 +6,10 @@
{{- range $path, $bytes := .Files.Glob "crds/monitoring.coreos.com*.yaml" }}
{{ $.Files.Get $path }}
{{- end }}
{{- end -}}
{{- if .Values.crd.dns }}
{{- range $path, $bytes := .Files.Glob "crds/dns.openstack.org*.yaml" }}
{{ $.Files.Get $path }}
{{- end }}
{{- end -}}

View File

@ -1,2 +1,3 @@
crd:
monitoring: true
dns: true

View File

@ -0,0 +1,59 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: designates.dns.openstack.org
spec:
group: dns.openstack.org
names:
kind: Designate
listKind: DesignateList
plural: designates
singular: designate
scope: Namespaced
validation:
openAPIV3Schema:
description: Designate is the Schema for the Designates API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: DesignateSpec defines the desired state of Designate
properties:
cloudname:
type: string
credentials:
type: string
required:
- cloudname
- credentials
type: object
status:
description: DesignateStatus defines the observed state of Designate
type: object
type: object
version: v1
versions:
- name: v1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -0,0 +1,71 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: zones.dns.openstack.org
spec:
group: dns.openstack.org
names:
kind: Zone
listKind: ZoneList
plural: zones
singular: zone
scope: Namespaced
validation:
openAPIV3Schema:
description: Zone is the Schema for the Zones API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ZoneSpec defines the desired state of Zone
properties:
description:
type: string
domain:
type: string
email:
type: string
ttl:
type: integer
type:
type: string
required:
- domain
- email
- ttl
type: object
status:
description: ZoneStatus defines the observed state of Zone
properties:
zoneId:
type: string
required:
- zoneId
type: object
type: object
version: v1
versions:
- name: v1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -7,6 +7,8 @@ resources:
- bases/infrastructure.vexxhost.cloud_rabbitmqs.yaml
- bases/monitoring.coreos.com_podmonitors.yaml
- bases/monitoring.coreos.com_prometheusrules.yaml
- bases/dns.openstack.org_zones.yaml
- bases/dns.openstack.org_designates.yaml
# +kubebuilder:scaffold:crdkustomizeresource
patchesStrategicMerge:

View File

@ -43,6 +43,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
@ -56,6 +68,46 @@ rules:
- patch
- update
- watch
- apiGroups:
- dns.openstack.org
resources:
- designates
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- dns.openstack.org
resources:
- designates/status
verbs:
- get
- patch
- update
- apiGroups:
- dns.openstack.org
resources:
- zones
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- dns.openstack.org
resources:
- zones/status
verbs:
- get
- patch
- update
- apiGroups:
- infrastructure.vexxhost.cloud
resources:

View File

@ -0,0 +1,9 @@
apiVersion: dns.openstack.org/v1
kind: Designate
metadata:
name: sample
annotations:
"dns.openstack.org/is-default-designate": "true"
spec:
credentials: rc-secret-sample
cloudname: devstack

View File

@ -0,0 +1,10 @@
apiVersion: dns.openstack.org/v1
kind: Zone
metadata:
name: sample
annotations:
"dns.openstack.org/designate": "sample"
spec:
domain: example2.com.
ttl: 3600
email: okozachenko@gmail.com

View File

@ -0,0 +1,178 @@
package controllers
import (
"context"
"fmt"
"strings"
"time"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
dnsv1 "opendev.org/vexxhost/openstack-operator/api/dns/v1"
"opendev.org/vexxhost/openstack-operator/builders"
"opendev.org/vexxhost/openstack-operator/utils/baseutils"
"opendev.org/vexxhost/openstack-operator/utils/k8sutils"
"opendev.org/vexxhost/openstack-operator/utils/openstackutils"
)
// DesignateReconciler reconciles a Designate object
type DesignateReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
DesignateClient *openstackutils.DesignateClientBuilder
}
const (
_autoReconcilePeriod = 15 * time.Second
_designatingAnnotation = "dns.openstack.org/designate"
_defaultDesignatingAnnotation = "dns.openstack.org/is-default-designate"
)
// +kubebuilder:rbac:groups=dns.openstack.org,resources=designates,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=dns.openstack.org,resources=designates/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=dns.openstack.org,resources=zones,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=dns.openstack.org,resources=zones/status,verbs=get;update;patch
// Reconcile does the reconcilication of designate instances
func (r *DesignateReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
var (
credentials corev1.Secret
designate dnsv1.Designate
)
ctx := context.Background()
log := r.Log.WithValues("Designate", req.NamespacedName)
labels := map[string]string{
"app.kubernetes.io/name": "designate",
"app.kubernetes.io/managed-by": "openstack-operator",
}
// 1 Get designate instance
if err := r.Get(ctx, req.NamespacedName, &designate); err != nil {
log.Error(err, "unable to fetch designate "+req.Name+":"+req.Namespace)
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 2 Get credentials
if err := r.Get(ctx, types.NamespacedName{
Namespace: req.Namespace,
Name: designate.Spec.Credentials,
}, &credentials); err != nil {
log.Error(err, "unable to fetch rc secret "+designate.Spec.Credentials+":"+req.Namespace)
return ctrl.Result{}, err
}
credential, ok := credentials.Data["clouds.yaml"]
if !ok {
err := fmt.Errorf("rc secret syntax error ")
log.Error(err, designate.Spec.Credentials+":"+designate.Spec.CloudName)
return ctrl.Result{}, err
}
// 3 Get designate client
if err := openstackutils.DesignateClient(r.DesignateClient, credential, designate.Spec.CloudName); err != nil {
log.WithValues("resource", "designateClient").WithValues("op", "op").Info("ClientCreationFailed" + err.Error())
return ctrl.Result{}, err
}
// 4 Create zone CRs
// 4-1 Get zone list from the designate
desinateZoneSpeclist := map[string]dnsv1.ZoneSpec{}
desinateZoneNameList := []string{}
designateZones, err := r.DesignateClient.ListZone()
if err != nil {
log.WithValues("resource", "Zone").WithValues("op", "op").Info("Error: Get zone list in the designate" + err.Error())
return ctrl.Result{}, err
}
for _, zone := range designateZones {
desinateZoneNameList = append(desinateZoneNameList, zone.Name)
desinateZoneSpeclist[zone.Name] = dnsv1.ZoneSpec{
Domain: zone.Name,
Email: zone.Email,
TTL: zone.TTL,
}
}
log.Info("Get Zone list in the Designate")
log.Info("Zone list in the Designate" + fmt.Sprintf("%v", desinateZoneSpeclist))
// 4-2 Get zone list in the cluster
clusterZoneObjectMetalist := map[string]metav1.ObjectMeta{}
clusterZoneNameList := []string{}
clusterZones := &dnsv1.ZoneList{}
if err := r.List(context.Background(), clusterZones); err != nil {
log.WithValues("resource", "Zone").WithValues("op", "op").Info("Error: Get zone list in the cluster" + err.Error())
return ctrl.Result{}, err
}
for _, zone := range clusterZones.Items {
clusterZoneNameList = append(clusterZoneNameList, zone.Spec.Domain)
clusterZoneObjectMetalist[zone.Spec.Domain] = metav1.ObjectMeta{
Name: zone.Name,
Namespace: zone.Namespace,
}
}
log.Info("Zone list in the cluster" + fmt.Sprintf("%v", clusterZoneNameList))
clusterOnlyNameList, designateOnlyNameList := baseutils.CompareStrSlice(clusterZoneNameList, desinateZoneNameList)
log.Info("Zone list in the only cluster" + fmt.Sprintf("%v", clusterOnlyNameList))
log.Info("Zone list in the only designate" + fmt.Sprintf("%v", designateOnlyNameList))
// 4-3 Create zone list (designateOnlyNameList) in the cluster
log.Info("Create Zone list in the cluster")
for _, zoneName := range designateOnlyNameList {
Zone := &dnsv1.Zone{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: strings.ReplaceAll(zoneName[:len(zoneName)-1], ".", "-"),
},
}
op, err := k8sutils.CreateOrUpdate(ctx, r, Zone, func() error {
return builders.Zone(Zone, &designate, r.Scheme).
Labels(labels).
Annotation(_designatingAnnotation, req.Name).
Domain(zoneName).
TTL(desinateZoneSpeclist[zoneName].TTL).
Email(desinateZoneSpeclist[zoneName].Email).
Build()
})
if err != nil {
return ctrl.Result{}, err
}
log.WithValues("resource", "Zone").WithValues("op", op).Info("Reconciled")
// err = r.Create(context.Background(), Zone)
// if err != nil {
// log.WithValues("resource", "Zone").WithValues("op", "op").Info("ZoneCreationFailed on Cluster -" + zoneName + ":" + err.Error())
// return ctrl.Result{}, err
// }
}
// 4-4 Delete zone list (clusterOnlyNameList) in the cluster
log.Info("Delete Zone list in the cluster")
for _, zoneName := range clusterOnlyNameList {
Zone := &dnsv1.Zone{
ObjectMeta: clusterZoneObjectMetalist[zoneName],
}
err = r.Delete(context.Background(), Zone)
if err != nil {
log.WithValues("resource", "Zone").WithValues("op", "op").Info("ZoneCreationFailed on Cluster -" + zoneName + ":" + err.Error())
return ctrl.Result{}, err
}
}
return ctrl.Result{Requeue: true, RequeueAfter: _autoReconcilePeriod}, nil
}
// SetupWithManager initializes the controller with primary manager
func (r *DesignateReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&dnsv1.Designate{}).
Owns(&dnsv1.Zone{}).
Complete(r)
}

View File

@ -0,0 +1,128 @@
package controllers
import (
"context"
"errors"
"fmt"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
dnsv1 "opendev.org/vexxhost/openstack-operator/api/dns/v1"
"opendev.org/vexxhost/openstack-operator/utils/baseutils"
"opendev.org/vexxhost/openstack-operator/utils/openstackutils"
)
// ZoneReconciler reconciles a Zone object
type ZoneReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
DesignateClient *openstackutils.DesignateClientBuilder
}
// +kubebuilder:rbac:groups=dns.openstack.org,resources=zones,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=dns.openstack.org,resources=zones/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete
// Reconcile does the reconcilication for create/update/delete Zone instances
func (r *ZoneReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
var (
Zone dnsv1.Zone
designateName string
credentials corev1.Secret
designate dnsv1.Designate
)
ctx := context.Background()
log := r.Log.WithValues("zone", req.NamespacedName)
// Get Zone
if err := r.Get(ctx, req.NamespacedName, &Zone); err != nil {
log.Error(err, "unable to fetch Zone"+req.Name+":"+req.Namespace)
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Find the corresponding designate Name
if val, ok := Zone.Annotations[_designatingAnnotation]; ok {
designateName = val
} else if val, ok := Zone.Annotations[_defaultDesignatingAnnotation]; ok {
designateName = val
} else {
err := errors.New("no designate annotation")
log.Error(err, "No designate annotation."+req.Name+":"+req.Namespace)
return ctrl.Result{}, err
}
// Get designate instance
if err := r.Get(ctx, types.NamespacedName{
Namespace: req.Namespace,
Name: designateName,
}, &designate); err != nil {
log.Error(err, "unable to fetch corresponding designate "+req.Name+":"+req.Namespace)
return ctrl.Result{}, err
}
// 2 Get credentials
if err := r.Get(ctx, types.NamespacedName{
Namespace: req.Namespace,
Name: designate.Spec.Credentials,
}, &credentials); err != nil {
log.Error(err, "unable to fetch rc secret "+designate.Spec.Credentials+":"+req.Namespace)
return ctrl.Result{}, err
}
credential, ok := credentials.Data["clouds.yaml"]
if !ok {
err := fmt.Errorf("rc secret syntax error ")
log.Error(err, designate.Spec.Credentials+":"+designate.Spec.CloudName)
return ctrl.Result{}, err
}
// 3 Get designate client
if err := openstackutils.DesignateClient(r.DesignateClient, credential, designate.Spec.CloudName); err != nil {
log.WithValues("resource", "designateClient").WithValues("op", "op").Info("ClientCreationFailed" + err.Error())
return ctrl.Result{}, err
}
// Use Finalizer for the async deletion
zoneFinalizeName := "zone.finalizers.dns.openstack.org"
if Zone.ObjectMeta.DeletionTimestamp.IsZero() {
if !(baseutils.ContainsString(Zone.ObjectMeta.Finalizers, zoneFinalizeName)) {
Zone.ObjectMeta.Finalizers = append(Zone.ObjectMeta.Finalizers, zoneFinalizeName)
if err := r.Update(ctx, &Zone); err != nil {
return ctrl.Result{}, err
}
}
} else {
if baseutils.ContainsString(Zone.ObjectMeta.Finalizers, zoneFinalizeName) {
if err := r.DesignateClient.DeleteZone(Zone.Spec.Domain); err != nil {
return ctrl.Result{}, err
}
log.Info("Zone deletion using finalizer")
Zone.ObjectMeta.Finalizers = baseutils.RemoveString(Zone.ObjectMeta.Finalizers, zoneFinalizeName)
if err := r.Update(ctx, &Zone); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
// Create or update
if err := r.DesignateClient.CreateOrUpdateZone(Zone.Spec.Domain, Zone.Spec.TTL, Zone.Spec.Email); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// SetupWithManager initializes the controller with primary manager
func (r *ZoneReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&dnsv1.Zone{}).
Complete(r)
}

6
go.mod
View File

@ -4,9 +4,15 @@ go 1.13
require (
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf
github.com/ghodss/yaml v1.0.0
github.com/go-logr/logr v0.1.0
github.com/google/go-cmp v0.3.0
github.com/gophercloud/gophercloud v0.1.0
github.com/onsi/ginkgo v1.11.0
github.com/onsi/gomega v1.8.1
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.5.1
gopkg.in/yaml.v2 v2.2.4
k8s.io/api v0.17.2
k8s.io/apimachinery v0.17.2
k8s.io/client-go v0.17.2

3
go.sum
View File

@ -66,6 +66,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
@ -151,6 +152,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
@ -248,6 +250,7 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=

75
main.go
View File

@ -1,6 +1,4 @@
/*
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
@ -26,9 +24,11 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
dnsv1 "opendev.org/vexxhost/openstack-operator/api/dns/v1"
monitoringv1 "opendev.org/vexxhost/openstack-operator/api/monitoring/v1"
infrastructurev1alpha1 "opendev.org/vexxhost/openstack-operator/api/v1alpha1"
"opendev.org/vexxhost/openstack-operator/controllers"
"opendev.org/vexxhost/openstack-operator/utils/openstackutils"
"opendev.org/vexxhost/openstack-operator/version"
// +kubebuilder:scaffold:imports
)
@ -40,9 +40,9 @@ var (
func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = infrastructurev1alpha1.AddToScheme(scheme)
_ = monitoringv1.AddToScheme(scheme)
_ = dnsv1.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
@ -54,7 +54,6 @@ func main() {
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
// Create manager
@ -70,8 +69,53 @@ func main() {
os.Exit(1)
}
// Get Designate client
designateClientBuilder := new(openstackutils.DesignateClientBuilder)
designateClientBuilder.SetAuthFailed()
// Setup controllers with manager
if err = (&controllers.McrouterReconciler{
setupMcrouterReconciler(mgr)
setupMemcachedReconciler(mgr)
setupZoneReconciler(mgr, designateClientBuilder)
setupDesignateReconciler(mgr, designateClientBuilder)
// +kubebuilder:scaffold:builder
setupLog.Info("starting manager", "revision", version.Revision)
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
// setupZoneReconciler setups the Zone controller with manager
func setupZoneReconciler(mgr ctrl.Manager, designateClientBuilder *openstackutils.DesignateClientBuilder) {
if err := (&controllers.ZoneReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Zone"),
Scheme: mgr.GetScheme(),
DesignateClient: designateClientBuilder,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Zone")
os.Exit(1)
}
}
// setupDesignateReconciler setups the Designate controller with manager
func setupDesignateReconciler(mgr ctrl.Manager, designateClientBuilder *openstackutils.DesignateClientBuilder) {
if err := (&controllers.DesignateReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Zone"),
Scheme: mgr.GetScheme(),
DesignateClient: designateClientBuilder,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Designate")
os.Exit(1)
}
}
// setupMcrouterReconciler setups the Mcrouter controller with manager
func setupMcrouterReconciler(mgr ctrl.Manager) {
if err := (&controllers.McrouterReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Mcrouter"),
Scheme: mgr.GetScheme(),
@ -79,8 +123,11 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "Mcrouter")
os.Exit(1)
}
}
if err = (&controllers.MemcachedReconciler{
// setupMemcachedReconciler setups the Memcached controller with manager
func setupMemcachedReconciler(mgr ctrl.Manager) {
if err := (&controllers.MemcachedReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Memcached"),
Scheme: mgr.GetScheme(),
@ -88,20 +135,4 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "Memcached")
os.Exit(1)
}
if err = (&controllers.RabbitmqReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Rabbitmq"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Rabbitmq")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
setupLog.Info("starting manager", "revision", version.Revision)
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}

15
utils/baseutils/codec.go Normal file
View File

@ -0,0 +1,15 @@
package baseutils
import (
"encoding/base64"
)
// Base64DecodeByte2Str returns plain text as string from the encrypted text as byte array
func Base64DecodeByte2Str(enc []byte) string {
encStr := string(enc)
decStr, err := base64.StdEncoding.DecodeString(encStr)
if err != nil {
return ""
}
return string(decStr)
}

54
utils/baseutils/slice.go Normal file
View File

@ -0,0 +1,54 @@
package baseutils
// CompareStrSlice compares two string slices and return the different elements
// Return values are 2 arrays; aOnlySlice, and bOnlySlice
func CompareStrSlice(aS []string, bS []string) ([]string, []string) {
aOnlyS := []string{}
for _, a := range aS {
i, isExist := Find(bS, a)
if !isExist {
aOnlyS = append(aOnlyS, a)
} else {
RemoveElement(&bS, i)
}
}
return aOnlyS, bS
}
// Find is a helper function to find the string in a slice of strings.
func Find(slice []string, val string) (int, bool) {
for i, item := range slice {
if item == val {
return i, true
}
}
return -1, false
}
// RemoveElement is a helper function to remove the ith string from a slice of strings.
func RemoveElement(a *[]string, i int) {
(*a)[i] = (*a)[len(*a)-1] // Copy last element to index i.
(*a)[len(*a)-1] = "" // Erase last element (write zero value).
(*a) = (*a)[:len(*a)-1] // Truncate the length
}
// ContainsString is a helper function to check string in a slice of strings
func ContainsString(slice []string, s string) bool {
for _, item := range slice {
if item == s {
return true
}
}
return false
}
// RemoveString is a helper function to remove string from a slice of strings.
func RemoveString(slice []string, s string) (result []string) {
for _, item := range slice {
if item == s {
continue
}
result = append(result, item)
}
return
}

View File

@ -0,0 +1,297 @@
package openstackutils
import (
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"opendev.org/vexxhost/openstack-operator/utils/tlsutils"
)
const (
_openAPIVersion = "3"
)
// DesignateClientBuilder is an implementation of the designateClientInterface
type DesignateClientBuilder struct {
ServiceClient *gophercloud.ServiceClient
isAuth bool
}
// CloudYAML is for parsing the clouds.yaml
type CloudYAML struct {
Clouds map[string]struct {
Auth struct {
Auth_url string `yaml:"auth_url"`
Project_name string `yaml:"project_name"`
Project_id string `yaml:"project_id"`
Username string `yaml:"username"`
Password string `yaml:"password"`
User_domain_name string `yaml:"user_domain_name"`
Project_domain_name string `yaml:"project_domain_name"`
} `yaml:"auth"`
Region_name string `yaml:"region_name"`
Interface string `yaml:"interface"`
} `yaml:"clouds"`
}
// DesignateClient is a constructor for the DesignateBuilder
func DesignateClient(existing *DesignateClientBuilder, rc []byte, cloudName string) error {
if existing.GetAuthStatus() {
log.Infof("Already authenticated")
return nil
}
if err := setAuthSettings(rc, cloudName); err != nil {
log.Infof("Authentication failed - %s", err.Error())
return err
}
serviceClient, err := createDesignateServiceClient()
if err != nil {
log.Infof("createDesignateServiceClient failed - %s", err.Error())
return err
}
existing.ServiceClient = serviceClient
existing.SetAuthSuccess()
log.Infof("Authentication success!")
return nil
}
// CreateZone creates a zone
func (c *DesignateClientBuilder) CreateZone(dn string, ttl int, email string) (string, error) {
// zone create
createOpts := zones.CreateOpts{
Name: dn,
Email: email,
Type: "PRIMARY",
TTL: ttl,
Description: "This is a zone.",
}
res := zones.Create(c.ServiceClient, createOpts)
if res.Err != nil {
log.Errorf("Create Zone failed - %s", res.Err.Error())
c.SetAuthFailed()
return "", res.Err
}
log.Infof("Gained zone infor successfully")
zoneInfo, err := res.Extract()
if err != nil {
c.SetAuthFailed()
log.Errorf("Extract zone infor failed")
return "", err
}
return zoneInfo.ID, err
}
// UpdateZone updates zone TTL and Email.
func (c *DesignateClientBuilder) UpdateZone(zoneID string, TTL int, Email string) error {
updateOpts := zones.UpdateOpts{
TTL: TTL,
Email: Email,
}
if err := zones.Update(c.ServiceClient, zoneID, updateOpts).Err; err != nil {
log.Errorf("Update zone failed")
c.SetAuthFailed()
return err
}
return nil
}
// DeleteZone deletes a zone
func (c *DesignateClientBuilder) DeleteZone(Domain string) error {
zoneList, err := c.ListZone()
if err != nil {
return err
}
for _, zone := range zoneList {
if zone.Name == Domain {
return c.deleteZoneByID(zone.ID)
}
}
log.Infof("No such zone exists to delete.")
return nil
}
// ListZone gets the zone list
func (c *DesignateClientBuilder) ListZone() ([]zones.Zone, error) {
listOpts := zones.ListOpts{}
allPages, err := zones.List(c.ServiceClient, listOpts).AllPages()
if err != nil {
log.Errorf("List zone list failed")
c.SetAuthFailed()
return []zones.Zone{}, err
}
allZones, err := zones.ExtractZones(allPages)
if err != nil {
log.Errorf("Extract zone infor from the zone list failed")
c.SetAuthFailed()
return []zones.Zone{}, err
}
return allZones, nil
}
// CreateOrUpdateZone sync the zone list
func (c *DesignateClientBuilder) CreateOrUpdateZone(Domain string, TTL int, Email string) error {
zoneList, err := c.ListZone()
if err != nil {
return err
}
for _, zone := range zoneList {
if Domain == zone.Name {
// Update Zone
log.Infof("Designate: Zone %s already exists", zone.Name)
return c.UpdateZone(zone.ID, TTL, Email)
}
}
// Create zone
_, err = c.CreateZone(Domain, TTL, Email)
return err
}
// deleteZoneByID deletes the zone using zoneID, consuming the designate API directly
func (c *DesignateClientBuilder) deleteZoneByID(zoneID string) error {
if err := zones.Delete(c.ServiceClient, zoneID).Err; err != nil {
c.SetAuthFailed()
return err
}
return nil
}
// SetAuthSuccess means the current client already authenticated
func (c *DesignateClientBuilder) SetAuthSuccess() {
c.isAuth = true
}
// SetAuthFailed means the current client needs to authenticate
func (c *DesignateClientBuilder) SetAuthFailed() {
c.isAuth = false
}
// GetAuthStatus returns the authentication status
func (c *DesignateClientBuilder) GetAuthStatus() bool {
return c.isAuth
}
// authenticate in OpenStack and obtain Designate service endpoint
func createDesignateServiceClient() (*gophercloud.ServiceClient, error) {
opts, err := getAuthSettings()
if err != nil {
return nil, err
}
log.Infof("Using OpenStack Keystone at %s", opts.IdentityEndpoint)
authProvider, err := openstack.NewClient(opts.IdentityEndpoint)
if err != nil {
return nil, err
}
tlsConfig, err := tlsutils.CreateTLSConfig("OPENSTACK")
if err != nil {
return nil, err
}
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: tlsConfig,
}
authProvider.HTTPClient.Transport = transport
if err = openstack.Authenticate(authProvider, opts); err != nil {
return nil, err
}
eo := gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
}
client, err := openstack.NewDNSV2(authProvider, eo)
if err != nil {
return nil, err
}
log.Infof("Found OpenStack Designate service at %s", client.Endpoint)
return client, nil
}
// returns OpenStack Keystone authentication settings by obtaining values from standard environment variables.
// also fixes incompatibilities between gophercloud implementation and *-stackrc files that can be downloaded
// from OpenStack dashboard in latest versions
func getAuthSettings() (gophercloud.AuthOptions, error) {
remapEnv(map[string]string{
"OS_TENANT_NAME": "OS_PROJECT_NAME",
"OS_TENANT_ID": "OS_PROJECT_ID",
"OS_DOMAIN_NAME": "OS_USER_DOMAIN_NAME",
"OS_DOMAIN_ID": "OS_USER_DOMAIN_ID",
})
opts, err := openstack.AuthOptionsFromEnv()
if err != nil {
return gophercloud.AuthOptions{}, err
}
opts.AllowReauth = true
if !strings.HasSuffix(opts.IdentityEndpoint, "/") {
opts.IdentityEndpoint += "/"
}
if !strings.HasSuffix(opts.IdentityEndpoint, "/v2.0/") && !strings.HasSuffix(opts.IdentityEndpoint, "/v3/") {
opts.IdentityEndpoint += "v2.0/"
}
return opts, nil
}
// copies environment variables to new names without overwriting existing values
func remapEnv(mapping map[string]string) {
for k, v := range mapping {
currentVal := os.Getenv(k)
newVal := os.Getenv(v)
if currentVal == "" && newVal != "" {
os.Setenv(k, newVal)
}
}
}
func setAuthSettings(rc []byte, cloudName string) error {
var cloudYaml CloudYAML
parseCloudYAML(rc, &cloudYaml)
credential, ok := cloudYaml.Clouds[cloudName]
if !ok {
return fmt.Errorf("rc secret does not involve the current cloud credential ")
}
os.Setenv("OS_AUTH_URL", credential.Auth.Auth_url)
os.Setenv("OS_PROJECT_ID", credential.Auth.Project_id)
os.Setenv("OS_PROJECT_NAME", credential.Auth.Project_name)
os.Setenv("OS_USER_DOMAIN_NAME", credential.Auth.User_domain_name)
os.Setenv("OS_USERNAME", credential.Auth.Username)
os.Setenv("OS_PASSWORD", credential.Auth.Password)
os.Setenv("OS_REGION_NAME", credential.Region_name)
os.Setenv("OS_INTERFACE", credential.Interface)
os.Setenv("OS_IDENTITY_API_VERSION", _openAPIVersion)
return nil
}
func parseCloudYAML(y []byte, cloudYaml *CloudYAML) {
err := yaml.Unmarshal([]byte(y), cloudYaml)
if err != nil {
panic(err)
}
}

73
utils/tlsutils/tls.go Normal file
View File

@ -0,0 +1,73 @@
package tlsutils
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
)
const defaultMinVersion = 0
// CreateTLSConfig creates tls.Config instance from TLS parameters passed in environment variables with the given prefix
func CreateTLSConfig(prefix string) (*tls.Config, error) {
caFile := os.Getenv(fmt.Sprintf("%s_CA_FILE", prefix))
certFile := os.Getenv(fmt.Sprintf("%s_CERT_FILE", prefix))
keyFile := os.Getenv(fmt.Sprintf("%s_KEY_FILE", prefix))
serverName := os.Getenv(fmt.Sprintf("%s_TLS_SERVER_NAME", prefix))
isInsecureStr := strings.ToLower(os.Getenv(fmt.Sprintf("%s_TLS_INSECURE", prefix)))
isInsecure := isInsecureStr == "true" || isInsecureStr == "yes" || isInsecureStr == "1"
tlsConfig, err := NewTLSConfig(certFile, keyFile, caFile, serverName, isInsecure, defaultMinVersion)
if err != nil {
return nil, err
}
return tlsConfig, nil
}
// NewTLSConfig creates a tls.Config instance from directly-passed parameters, loading the ca, cert, and key from disk
func NewTLSConfig(certPath, keyPath, caPath, serverName string, insecure bool, minVersion uint16) (*tls.Config, error) {
if certPath != "" && keyPath == "" || certPath == "" && keyPath != "" {
return nil, errors.New("either both cert and key or none must be provided")
}
var certificates []tls.Certificate
if certPath != "" {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, fmt.Errorf("could not load TLS cert: %s", err)
}
certificates = append(certificates, cert)
}
roots, err := loadRoots(caPath)
if err != nil {
return nil, err
}
return &tls.Config{
MinVersion: minVersion,
Certificates: certificates,
RootCAs: roots,
InsecureSkipVerify: insecure,
ServerName: serverName,
}, nil
}
// loads CA cert
func loadRoots(caPath string) (*x509.CertPool, error) {
if caPath == "" {
return nil, nil
}
roots := x509.NewCertPool()
pem, err := ioutil.ReadFile(caPath)
if err != nil {
return nil, fmt.Errorf("error reading %s: %s", caPath, err)
}
ok := roots.AppendCertsFromPEM(pem)
if !ok {
return nil, fmt.Errorf("could not read root certs: %s", err)
}
return roots, nil
}