-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
<!-- Thank you for contributing to Catalog! Note: 1. With pull requests: - Open your pull request against "main" - Your pull request should have no more than three commits, if not you should squash them. - It should pass all tests in the available continuous integration systems such as GitHub Actions. - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README. 2. Please create an issue first to describe the problem. We recommend that link the issue with the PR in the following question. For more info, check https://kusionstack.io/docs/governance/contribute/ --> ## What type of PR is this? <!-- Add one of the following kinds: /kind bug /kind cleanup /kind documentation /kind feature /kind chore --> ## What this PR does / why we need it: ## Which issue(s) this PR fixes: <!-- *Automatically closes linked issue when PR is merged. Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`. _If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_* --> Fixes # ## Special notes for your reviewer: ### Does this PR introduce a user-facing change? <!-- If no, just write "NONE" in the release-note block below. If yes, a release note is required: Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required". --> ```release-note ``` ### Additional documentation e.g., design docs, usage docs, etc.: <!-- Please use the following format for linking documentation: - [Design]: <link> - [Usage]: <link> - [Other doc]: <link> --> ```docs ```
- Loading branch information
Showing
6 changed files
with
505 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
TEST?=$$(go list ./... | grep -v 'vendor') | ||
###### chang variables below according to your own modules ### | ||
NAMESPACE=kusionstack | ||
NAME=opsrule | ||
VERSION=v0.1.0 | ||
BINARY=../bin/kusion-module-${NAME}_${VERSION} | ||
|
||
LOCAL_ARCH := $(shell uname -m) | ||
ifeq ($(LOCAL_ARCH),x86_64) | ||
GOARCH_LOCAL := amd64 | ||
else | ||
GOARCH_LOCAL := $(LOCAL_ARCH) | ||
endif | ||
export GOOS_LOCAL := $(shell uname|tr 'A-Z' 'a-z') | ||
export OS_ARCH ?= $(GOARCH_LOCAL) | ||
|
||
default: install | ||
|
||
build-darwin: | ||
GOOS=darwin GOARCH=arm64 go build -o ${BINARY} ./${NAME} | ||
|
||
install: build-darwin | ||
# copy module binary to $KUSION_HOME. e.g. ~/.kusion/modules/kusionstack/opsrule/v0.1.0/darwin/arm64/kusion-module-opsrule_v0.1.0 | ||
mkdir -p ${KUSION_HOME}/modules/${NAMESPACE}/${NAME}/${VERSION}/${GOOS_LOCAL}/${OS_ARCH} | ||
cp ${BINARY} ${KUSION_HOME}/modules/${NAMESPACE}/${NAME}/${VERSION}/${GOOS_LOCAL}/${OS_ARCH} | ||
|
||
test: | ||
TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 5m |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package generators | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/go-plugin" | ||
"gopkg.in/yaml.v2" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
v1 "kusionstack.io/kusion/pkg/apis/core/v1" | ||
"kusionstack.io/kusion/pkg/apis/core/v1/workload" | ||
"kusionstack.io/kusion/pkg/log" | ||
"kusionstack.io/kusion/pkg/modules" | ||
"kusionstack.io/kusion/pkg/modules/proto" | ||
) | ||
|
||
// HandshakeConfig is a common handshake that is shared by plugin and host. | ||
var HandshakeConfig = plugin.HandshakeConfig{ | ||
ProtocolVersion: 1, | ||
MagicCookieKey: "MODULE_PLUGIN", | ||
MagicCookieValue: "ON", | ||
} | ||
|
||
func StartModule(module modules.Module) { | ||
plugin.Serve(&plugin.ServeConfig{ | ||
HandshakeConfig: HandshakeConfig, | ||
Plugins: map[string]plugin.Plugin{ | ||
modules.PluginKey: &modules.GRPCPlugin{Impl: module}, | ||
}, | ||
|
||
// A non-nil value here enables gRPC serving for this plugin... | ||
GRPCServer: plugin.DefaultGRPCServer, | ||
}) | ||
} | ||
|
||
type GeneratorRequest struct { | ||
// Project represents the project name | ||
Project string `json:"project,omitempty"` | ||
// Stack represents the stack name | ||
Stack string `json:"stack,omitempty"` | ||
// App represents the application name, which is typically the same as the namespace of Kubernetes resources | ||
App string `json:"app,omitempty"` | ||
// Workload represents the workload configuration | ||
Workload *workload.Workload `json:"workload,omitempty"` | ||
// DevModuleConfig is the developer's inputs of this module | ||
DevModuleConfig v1.Accessory `json:"dev_module_config,omitempty"` | ||
// PlatformModuleConfig is the platform engineer's inputs of this module | ||
PlatformModuleConfig v1.GenericConfig `json:"platform_module_config,omitempty"` | ||
// RuntimeConfig is the runtime configurations defined in the workspace config | ||
RuntimeConfig *v1.RuntimeConfigs `json:"runtime_config,omitempty"` | ||
} | ||
|
||
func NewGeneratorRequest(req *proto.GeneratorRequest) (*GeneratorRequest, error) { | ||
|
||
log.Infof("module proto request received:%s", req.String()) | ||
|
||
// validate workload | ||
if req.Workload == nil { | ||
return nil, fmt.Errorf("workload in the request is nil") | ||
} | ||
w := &workload.Workload{} | ||
if err := yaml.Unmarshal(req.Workload, w); err != nil { | ||
return nil, fmt.Errorf("unmarshal workload failed. %w", err) | ||
} | ||
|
||
var dc v1.Accessory | ||
if req.DevModuleConfig != nil { | ||
if err := yaml.Unmarshal(req.DevModuleConfig, &dc); err != nil { | ||
return nil, fmt.Errorf("unmarshal dev module config failed. %w", err) | ||
} | ||
} | ||
|
||
var pc v1.GenericConfig | ||
if req.PlatformModuleConfig != nil { | ||
if err := yaml.Unmarshal(req.PlatformModuleConfig, &pc); err != nil { | ||
return nil, fmt.Errorf("unmarshal platform module config failed. %w", err) | ||
} | ||
} | ||
|
||
var rc *v1.RuntimeConfigs | ||
if req.RuntimeConfig != nil { | ||
if err := yaml.Unmarshal(req.RuntimeConfig, rc); err != nil { | ||
return nil, fmt.Errorf("unmarshal runtime config failed. %w", err) | ||
} | ||
} | ||
|
||
result := &GeneratorRequest{ | ||
Project: req.Project, | ||
Stack: req.Stack, | ||
App: req.App, | ||
Workload: w, | ||
DevModuleConfig: dc, | ||
PlatformModuleConfig: pc, | ||
RuntimeConfig: rc, | ||
} | ||
out, err := yaml.Marshal(result) | ||
if err != nil { | ||
return nil, fmt.Errorf("marshal new generator request failed. %w", err) | ||
} | ||
log.Infof("new generator request:%s", string(out)) | ||
return result, nil | ||
} | ||
|
||
func EmptyResponse() *proto.GeneratorResponse { | ||
return &proto.GeneratorResponse{} | ||
} | ||
|
||
func WrapK8sResourceToKusionResource(id string, resource any) (*v1.Resource, error) { | ||
gvk := resource.(runtime.Object).GetObjectKind().GroupVersionKind().String() | ||
|
||
// fixme: this function converts int to int64 by default | ||
unstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resource) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &v1.Resource{ | ||
ID: id, | ||
Type: v1.Kubernetes, | ||
Attributes: unstructured, | ||
DependsOn: nil, | ||
Extensions: map[string]any{ | ||
v1.ResourceExtensionGVK: gvk, | ||
}, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
module kusion-modules | ||
|
||
go 1.19 | ||
|
||
require ( | ||
github.com/hashicorp/go-plugin v1.6.0 | ||
gopkg.in/yaml.v3 v3.0.1 | ||
k8s.io/apimachinery v0.27.2 | ||
kusionstack.io/kube-api v0.1.1 | ||
kusionstack.io/kusion v0.10.1-0.20240305085131-a947ad87dfa8 | ||
) | ||
|
||
require ( | ||
github.com/docker/distribution v2.8.2+incompatible // indirect | ||
github.com/fatih/color v1.13.0 // indirect | ||
github.com/go-logr/logr v1.2.4 // indirect | ||
github.com/gogo/protobuf v1.3.2 // indirect | ||
github.com/golang/protobuf v1.5.3 // indirect | ||
github.com/google/gofuzz v1.2.0 // indirect | ||
github.com/hashicorp/go-hclog v0.16.2 // indirect | ||
github.com/hashicorp/yamux v0.1.1 // indirect | ||
github.com/json-iterator/go v1.1.12 // indirect | ||
github.com/kr/pretty v0.3.1 // indirect | ||
github.com/mattn/go-colorable v0.1.12 // indirect | ||
github.com/mattn/go-isatty v0.0.14 // indirect | ||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||
github.com/modern-go/reflect2 v1.0.2 // indirect | ||
github.com/oklog/run v1.0.0 // indirect | ||
github.com/opencontainers/go-digest v1.0.0 // indirect | ||
github.com/pkg/errors v0.9.1 // indirect | ||
go.uber.org/atomic v1.9.0 // indirect | ||
go.uber.org/multierr v1.6.0 // indirect | ||
go.uber.org/zap v1.24.0 // indirect | ||
golang.org/x/net v0.19.0 // indirect | ||
golang.org/x/sys v0.15.0 // indirect | ||
golang.org/x/text v0.14.0 // indirect | ||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect | ||
google.golang.org/grpc v1.58.3 // indirect | ||
google.golang.org/protobuf v1.31.0 // indirect | ||
gopkg.in/inf.v0 v0.9.1 // indirect | ||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect | ||
gopkg.in/yaml.v2 v2.4.0 // indirect | ||
k8s.io/api v0.27.2 // indirect | ||
k8s.io/klog/v2 v2.100.1 // indirect | ||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect | ||
sigs.k8s.io/controller-runtime v0.15.1 // indirect | ||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect | ||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"strconv" | ||
|
||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/util/intstr" | ||
"kusionstack.io/kube-api/apps/v1alpha1" | ||
v1 "kusionstack.io/kusion/pkg/apis/core/v1" | ||
"kusionstack.io/kusion/pkg/apis/core/v1/workload" | ||
"kusionstack.io/kusion/pkg/log" | ||
"kusionstack.io/kusion/pkg/modules" | ||
"kusionstack.io/kusion/pkg/modules/proto" | ||
jsonutil "kusionstack.io/kusion/pkg/util/json" | ||
|
||
generators "kusion-modules" | ||
) | ||
|
||
type OpsRuleModule struct{} | ||
|
||
func (o *OpsRuleModule) Generate(r *proto.GeneratorRequest) (*proto.GeneratorResponse, error) { | ||
emptyResponse := generators.EmptyResponse() | ||
request, err := generators.NewGeneratorRequest(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// opsRule does not exist in AppConfig and workspace config | ||
if request.DevModuleConfig == nil && request.PlatformModuleConfig == nil { | ||
log.Info("OpsRule does not exist in AppConfig and workspace config") | ||
return emptyResponse, nil | ||
} | ||
|
||
// Job does not support maxUnavailable | ||
if request.Workload.Header.Type == workload.TypeJob { | ||
log.Infof("Job does not support opsRule") | ||
return emptyResponse, nil | ||
} | ||
|
||
if request.Workload.Service.Type == workload.Collaset { | ||
maxUnavailable, err := GetMaxUnavailable(request.DevModuleConfig, request.PlatformModuleConfig) | ||
if err != nil { | ||
return nil, err | ||
} | ||
ptr := &v1alpha1.PodTransitionRule{ | ||
TypeMeta: metav1.TypeMeta{ | ||
APIVersion: v1alpha1.GroupVersion.String(), | ||
Kind: "PodTransitionRule", | ||
}, | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: modules.UniqueAppName(request.Project, request.Stack, request.App), | ||
Namespace: request.App, | ||
}, | ||
Spec: v1alpha1.PodTransitionRuleSpec{ | ||
Selector: &metav1.LabelSelector{ | ||
MatchLabels: modules.UniqueAppLabels(request.Project, request.App), | ||
}, | ||
Rules: []v1alpha1.TransitionRule{ | ||
{ | ||
Name: "maxUnavailable", | ||
TransitionRuleDefinition: v1alpha1.TransitionRuleDefinition{ | ||
AvailablePolicy: &v1alpha1.AvailableRule{ | ||
MaxUnavailableValue: &maxUnavailable, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
resourceID := modules.KubernetesResourceID(ptr.TypeMeta, ptr.ObjectMeta) | ||
resource, err := generators.WrapK8sResourceToKusionResource(resourceID, ptr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
str := jsonutil.Marshal2String(resource) | ||
b := []byte(str) | ||
return &proto.GeneratorResponse{ | ||
Resources: [][]byte{b}, | ||
}, nil | ||
} | ||
return emptyResponse, nil | ||
} | ||
|
||
func GetMaxUnavailable(devConfig v1.Accessory, platformConfig v1.GenericConfig) (intstr.IntOrString, error) { | ||
var maxUnavailable interface{} | ||
key := "maxUnavailable" | ||
|
||
// developer config | ||
// kusionstack/[email protected] : t.OpsRule { | ||
// maxUnavailable: "30%" | ||
// } | ||
if devConfig != nil && devConfig[key] != "" { | ||
maxUnavailable = devConfig[key] | ||
} else if platformConfig == nil { | ||
return intstr.IntOrString{}, nil | ||
} else { | ||
// platformConfig example | ||
// kusionstack/[email protected]: | ||
// maxUnavailable: 1 # or 10% | ||
maxUnavailable = platformConfig[key] | ||
} | ||
var mu string | ||
mu, isString := maxUnavailable.(string) | ||
if !isString { | ||
temp, isInt := maxUnavailable.(int) | ||
if isInt { | ||
mu = strconv.Itoa(temp) | ||
} else { | ||
return intstr.IntOrString{}, errors.New("illegal opsRule config. opsRule.maxUnavailable is not string or int") | ||
} | ||
} | ||
return intstr.Parse(mu), nil | ||
} | ||
|
||
func main() { | ||
modules.StartModule(&OpsRuleModule{}) | ||
} |
Oops, something went wrong.