Skip to content

Commit

Permalink
Merge pull request #191 from vshn/change/integrate_api_server
Browse files Browse the repository at this point in the history
Add backup API Server to main repo
  • Loading branch information
Kidswiss authored Jul 15, 2024
2 parents 02dd941 + 0ff9b6c commit 6ef4e93
Show file tree
Hide file tree
Showing 99 changed files with 25,654 additions and 2,064 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ appcat
:9443
# But don't ignore the appcat APIS!
!apis/appcat
!pkg/apiserver/appcat

# temp file, editor and IDE paraphernalia
.idea
Expand Down
17 changes: 15 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,22 @@ $(protoc_bin): | $(go_bin)
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

.PHONY: protobuf-gen
protobuf-gen: export PATH := $(go_bin):$(PATH)
protobuf-gen: $(protoc_bin)
go run k8s.io/code-generator/cmd/[email protected] \
--packages=github.com/vshn/appcat/v4/apis/apiserver/v1 \
--output-base=./.work/tmp \
--go-header-file=./pkg/apiserver/hack/boilerplate.txt \
--apimachinery-packages='-k8s.io/apimachinery/pkg/util/intstr,-k8s.io/apimachinery/pkg/api/resource,-k8s.io/apimachinery/pkg/runtime/schema,-k8s.io/apimachinery/pkg/runtime,-k8s.io/apimachinery/pkg/apis/meta/v1,-k8s.io/apimachinery/pkg/apis/meta/v1beta1,-k8s.io/api/core/v1,-k8s.io/api/rbac/v1' \
--proto-import=./.work/kubernetes/staging/src/ \
--proto-import=./.work/kubernetes/vendor && \
mv ./.work/tmp/github.com/vshn/appcat/v4/apis/apiserver/v1/generated.pb.go ./apis/apiserver/v1/ && \
rm -rf ./.work/tmp

.PHONY: generate
generate: export PATH := $(go_bin):$(PATH)
generate: $(protoc_bin) get-crds generate-stackgres-crds ## Generate code with controller-gen and protobuf.
generate: get-crds generate-stackgres-crds protobuf-gen ## Generate code with controller-gen and protobuf.
go version
rm -rf apis/generated
go run sigs.k8s.io/controller-tools/cmd/controller-gen paths="{./apis/v1/..., ./apis/vshn/..., ./apis/exoscale/...}" object crd:crdVersions=v1,allowDangerousTypes=true output:artifacts:config=./apis/generated
Expand Down Expand Up @@ -206,7 +219,7 @@ clean:
get-crds:
./hack/get_crds.sh https://github.com/crossplane-contrib/provider-helm provider-helm apis/release apis/helm
./hack/get_crds.sh https://github.com/crossplane-contrib/provider-kubernetes provider-kubernetes apis/object/v1alpha2 apis/kubernetes

# provider-sql needs manual fixes... Running this every time would break them.
# The crossplane code generator only works if the code is valid, but the code is not valid until the code generator has run...
#./hack/get_crds.sh https://github.com/crossplane-contrib/provider-sql provider-sql apis/ apis/sql
Expand Down
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,66 @@ Crank will return a list of all the objects this specific request would have pro

Please have a look at the `hack/` folder for an example.

# Run API Server locally
To run the API server on your local machine you need to register the IDE running instance with kind cluster.
This can be achieved with the following guide.

The `externalName` needs to be changed to your specific host IP.
When running kind on Linux you can find it with `docker inspect`.
On some docker distributions the host IP is accessible via `host.docker.internal`.
For Lima distribution the host IP is accessible via `host.lima.internal`.

```bash
# Run this command in kindev -> https://github.com/vshn/kindev
make appcat-apiserver
HOSTIP=$(docker inspect kindev-control-plane | jq '.[0].NetworkSettings.Networks.kind.Gateway')
# HOSTIP=host.docker.internal # On some docker distributions
# HOSTIP=host.lima.internal # On lima distributions
kind get kubeconfig --name kindev > ~/.kube/config
cat <<EOF | sed -e "s/172.21.0.1/$HOSTIP/g" | kubectl apply -f -
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1.api.appcat.vshn.io
labels:
api: appcat
apiserver: "true"
spec:
version: v1
group: api.appcat.vshn.io
insecureSkipTLSVerify: true
groupPriorityMinimum: 2000
service:
name: appcat
namespace: default
port: 9443
versionPriority: 10
---
apiVersion: v1
kind: Service
metadata:
name: appcat
namespace: default
spec:
ports:
- port: 9443
protocol: TCP
targetPort: 9443
type: ExternalName
externalName: 172.21.0.1 # Change to host IP
EOF
```

After the above steps just run the API server via IDE with the following arguments.
```
apiserver --secure-port=9443 --kubeconfig=<your-home-path>/.kube/config --authentication-kubeconfig=<your-home-path>/.kube/config --authorization-kubeconfig=<your-home-path>/.kube/config --feature-gates=APIPriorityAndFairness=false
```
### Protobuf installation
Protocol Buffers (Protobuf) is a free and open-source cross-platform data format used to serialize structured data.
Kubernetes internally uses gRPC clients with protobuf serialization. APIServer objects when handled internally in K8S
need to implement protobuf interface. The implementation of the interface is done by [code-generator](https://github.com/kubernetes/code-generator). Two dependencies are required to use this tool [protoc](https://github.com/protocolbuffers/protobuf) and [protoc-gen-go](https://google.golang.org/protobuf/cmd/protoc-gen-go).
187 changes: 187 additions & 0 deletions apis/apiserver/v1/appcat_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package v1

import (
"encoding/json"
"regexp"
"strings"

v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
"golang.org/x/text/cases"
"golang.org/x/text/language"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/apiserver-runtime/pkg/builder/resource"
)

// +kubebuilder:rbac:groups="apiextensions.crossplane.io",resources=compositions,verbs=get;list;watch
// +kubebuilder:rbac:groups="stackgres.io",resources=sgbackups,verbs=get;list;watch
// +kubebuilder:rbac:groups="k8up.io",resources=snapshots,verbs=get;list;watch
// +kubebuilder:rbac:groups="vshn.appcat.vshn.io",resources=vshnredis;xvshnpostgresqls;vshnmariadbs;,verbs=get;list;watch

var (
// OfferedValue is the label value to identify AppCat services
OfferedValue = "true"

// PrefixAppCatKey is the label and annotation prefix for AppCat services in compositions.
PrefixAppCatKey = "metadata.appcat.vshn.io"

// OfferedKey is the label key to identify AppCat services
OfferedKey = PrefixAppCatKey + "/offered"
)

// Resource is the name of this resource in plural form
var Resource = "appcats"

// +kubebuilder:object:root=true

// AppCat defines the main object for this API Server
// +k8s:openapi-gen=true
type AppCat struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Details `json:"details,omitempty"`
Plans map[string]VSHNPlan `json:"plans,omitempty"`
Status AppCatStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// AppCatList defines a list of AppCat
// +k8s:openapi-gen=true
type AppCatList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

Items []AppCat `json:"items"`
}

// Details are fields that are dynamically parsed from the annotations on a composition.
type Details map[string]string

// VSHNPlan represents a plan for a VSHN service.
// It ignores the scheduling labels and other internal fields.
// +k8s:openapi-gen=true
type VSHNPlan struct {
Note string `json:"note,omitempty"`
// JSize is called JSize because protobuf creates a method Size()
JSize VSHNSize `json:"size,omitempty"`
}

// VSHNSize describes the aspects of the actual plan.
// This needs to be a separate struct as the protobuf generator can't handle
// embedded struct apparently.
// +k8s:openapi-gen=true
type VSHNSize struct {
CPU string `json:"cpu,omitempty"`
Disk string `json:"disk,omitempty"`
Memory string `json:"memory,omitempty"`
}

// AppCat needs to implement the builder resource interface
var _ resource.Object = &AppCat{}

func (in *AppCat) GetObjectMeta() *metav1.ObjectMeta {
return &in.ObjectMeta
}

func (in *AppCat) NamespaceScoped() bool {
return false
}

func (in *AppCat) New() runtime.Object {
return &AppCat{}
}

func (in *AppCat) NewList() runtime.Object {
return &AppCatList{}
}

// GetGroupVersionResource returns the GroupVersionResource for this resource.
// The resource should be the all lowercase and pluralized kind
func (in *AppCat) GetGroupVersionResource() schema.GroupVersionResource {
return schema.GroupVersionResource{
Group: GroupVersion.Group,
Version: GroupVersion.Version,
Resource: Resource,
}
}

// IsStorageVersion returns true if the object is also the internal version -- i.e. is the type defined for the API group or an alias to this object.
// If false, the resource is expected to implement MultiVersionObject interface.
func (in *AppCat) IsStorageVersion() bool {
return true
}

var _ resource.ObjectList = &AppCatList{}

func (in *AppCatList) GetListMeta() *metav1.ListMeta {
return &in.ListMeta
}

// AppCatStatus defines the observed state of AppCat
// +k8s:openapi-gen=true
type AppCatStatus struct {
// CompositionName is the name of the composition
CompositionName string `json:"compositionName,omitempty"`
}

// NewAppCatFromComposition returns an AppCat based on the given composition
// If the composition does not satisfy one of its rules, the func will return nil
func NewAppCatFromComposition(comp *v1.Composition) *AppCat {
if comp == nil || comp.Labels == nil || comp.Labels[OfferedKey] != OfferedValue {
return nil
}

appCat := &AppCat{
ObjectMeta: *comp.ObjectMeta.DeepCopy(),
Status: AppCatStatus{
CompositionName: comp.Name,
},
Details: Details{},
}
appCat.Annotations = nil
appCat.Labels = nil
if comp.Annotations != nil {
for k, v := range comp.Annotations {
if k == PrefixAppCatKey+"/plans" {
parsePlansJSON(v, appCat)
continue
}
if strings.HasPrefix(k, PrefixAppCatKey) {
index := strings.LastIndex(k, "/")
appCat.Details[makeCamelCase(k[index+1:])] = v
}
}
}

return appCat
}

// makeCamelCase transforms any string in camel case string
func makeCamelCase(s string) string {
reg, _ := regexp.Compile("[^a-zA-Z0-9-]+")
s = reg.ReplaceAllString(s, "")
s = strings.ToLower(s)
slices := strings.FieldsFunc(s, func(c rune) bool { return c == '-' })
strCamel := slices[0]
for _, v := range slices[1:] {
strCamel += cases.Title(language.English).String(v)
}
return strCamel
}

func parsePlansJSON(jsonPlans string, spec *AppCat) {
plans := map[string]VSHNPlan{}

err := json.Unmarshal([]byte(jsonPlans), &plans)
if err != nil {
spec.Plans = map[string]VSHNPlan{
"Plans are currently not available": {},
}
return
}
spec.Plans = plans
}
Loading

0 comments on commit 6ef4e93

Please sign in to comment.