Skip to content

Commit

Permalink
Feat: add kompose client PoC (#1593)
Browse files Browse the repository at this point in the history
* fix: support host port and protocol in functional tests

* feat: add kompose client with options

Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>

* test: add options unit tests

Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>

* feat: add partial convert options

Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>

* feat: finish convert process

Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>

* test: finish unit tests of the kompose client

Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>

* remove unecessary changes

Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>

* feat: add generate network policies to client

Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>

* update go mod

Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>

---------

Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>
  • Loading branch information
AhmedGrati authored Aug 24, 2023
1 parent b83d4d4 commit ea80734
Show file tree
Hide file tree
Showing 12 changed files with 530 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,6 @@ tags
.idea

.DS_Store

# Client Test generated files
client/testdata/generated
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,7 @@ install-golangci-lint:
.PHONY: golangci-lint
golangci-lint: install-golangci-lint
$(GOLANGCI_LINT) run -c .golangci.yml --timeout 5m

.PHONY: test-client
test-client:
go test ./client/...
21 changes: 21 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package client

type Kompose struct {
suppressWarnings bool
verbose bool
errorOnWarning bool
}

func NewClient(opts ...Opt) (*Kompose, error) {
k := &Kompose{
suppressWarnings: false,
verbose: false,
errorOnWarning: false,
}
for _, op := range opts {
if err := op(k); err != nil {
return nil, err
}
}
return k, nil
}
248 changes: 248 additions & 0 deletions client/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package client

import (
"fmt"

"github.com/kubernetes/kompose/pkg/app"
"github.com/kubernetes/kompose/pkg/kobject"
"k8s.io/apimachinery/pkg/runtime"
)

func (k *Kompose) Convert(options ConvertOptions) ([]runtime.Object, error) {
options = k.setDefaultValues(options)
err := k.validateOptions(options)
if err != nil {
return nil, err
}
kobjectConvertOptions := kobject.ConvertOptions{
ToStdout: options.ToStdout,
CreateChart: k.createChart(options),
GenerateYaml: true,
GenerateJSON: options.GenerateJson,
Replicas: *options.Replicas,
InputFiles: options.InputFiles,
OutFile: options.OutFile,
Provider: k.getProvider(options),
CreateD: k.createDeployment(options),
CreateDS: k.createDaemonSet(options),
CreateRC: k.createReplicationController(options),
Build: *options.Build,
BuildRepo: k.buildRepo(options),
BuildBranch: k.buildBranch(options),
PushImage: options.PushImage,
PushImageRegistry: options.PushImageRegistry,
CreateDeploymentConfig: k.createDeploymentConfig(options),
EmptyVols: false,
Volumes: *options.VolumeType,
PVCRequestSize: options.PvcRequestSize,
InsecureRepository: k.insecureRepository(options),
IsDeploymentFlag: k.createDeployment(options),
IsDaemonSetFlag: k.createDaemonSet(options),
IsReplicationControllerFlag: k.createReplicationController(options),
Controller: k.getController(options),
IsReplicaSetFlag: *options.Replicas != 0,
IsDeploymentConfigFlag: k.createDeploymentConfig(options),
YAMLIndent: 2,
WithKomposeAnnotation: *options.WithKomposeAnnotations,
MultipleContainerMode: k.multiContainerMode(options),
ServiceGroupMode: k.serviceGroupMode(options),
ServiceGroupName: k.serviceGroupName(options),
SecretsAsFiles: k.secretsAsFiles(options),
GenerateNetworkPolicies: options.GenerateNetworkPolicies,
}
err = app.ValidateComposeFile(&kobjectConvertOptions)
if err != nil {
return nil, err
}
objects, err := app.Convert(kobjectConvertOptions)
return objects, err
}

func (k *Kompose) setDefaultValues(options ConvertOptions) ConvertOptions {
replicasDefaultValue := 1
buildDefaultValue := "none"
volumeTypeDefaultValue := "persistentVolumeClaim"
withKomposeAnnotationsDefaultValue := true
kubernetesControllerDefaultValue := "deployment"
kubernetesServiceGroupModeDefaultValue := ""

if options.Replicas == nil {
options.Replicas = &replicasDefaultValue
}
if options.Build == nil {
options.Build = &buildDefaultValue
}
if options.VolumeType == nil {
options.VolumeType = &volumeTypeDefaultValue
}
if options.WithKomposeAnnotations == nil {
options.WithKomposeAnnotations = &withKomposeAnnotationsDefaultValue
}
if options.Provider == nil {
options.Provider = Kubernetes{
Controller: &kubernetesControllerDefaultValue,
}
}
if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
if kubernetesProvider.Controller == nil {
options.Provider = Kubernetes{
Controller: &kubernetesControllerDefaultValue,
Chart: options.Provider.(Kubernetes).Chart,
MultiContainerMode: options.Provider.(Kubernetes).MultiContainerMode,
ServiceGroupMode: options.Provider.(Kubernetes).ServiceGroupMode,
ServiceGroupName: options.Provider.(Kubernetes).ServiceGroupName,
SecretsAsFiles: options.Provider.(Kubernetes).SecretsAsFiles,
}
}
if kubernetesProvider.ServiceGroupMode == nil {
options.Provider = Kubernetes{
Controller: options.Provider.(Kubernetes).Controller,
Chart: options.Provider.(Kubernetes).Chart,
MultiContainerMode: options.Provider.(Kubernetes).MultiContainerMode,
ServiceGroupMode: &kubernetesServiceGroupModeDefaultValue,
ServiceGroupName: options.Provider.(Kubernetes).ServiceGroupName,
SecretsAsFiles: options.Provider.(Kubernetes).SecretsAsFiles,
}
}
}
return options
}

func (k *Kompose) validateOptions(options ConvertOptions) error {
build := options.Build
if *build != string(LOCAL) && *build != string(BUILD_CONFIG) && *build != string(NONE) {
return fmt.Errorf(
"unexpected Value for Build field. Possible values are: %v, %v, and %v", string(LOCAL), string(BUILD_CONFIG), string(NONE),
)
}

volumeType := options.VolumeType
if *volumeType != string(PVC) && *volumeType != string(EMPTYDIR) && *volumeType != string(HOSTPATH) && *volumeType != string(CONFIGMAP) {
return fmt.Errorf(
"unexpected Value for VolumeType field. Possible values are: %v, %v, %v, %v", string(PVC), string(EMPTYDIR), string(HOSTPATH), string(CONFIGMAP),
)
}

if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
kubernetesController := kubernetesProvider.Controller
if *kubernetesController != string(DEPLOYMENT) && *kubernetesController != string(DAEMONSET) && *kubernetesController != string(REPLICATION_CONTROLLER) {
return fmt.Errorf(
"unexpected Value for Kubernetes Controller field. Possible values are: %v, %v, and %v", string(DEPLOYMENT), string(DAEMONSET), string(REPLICATION_CONTROLLER),
)
}

kubernetesServiceGroupMode := kubernetesProvider.ServiceGroupMode
if *kubernetesServiceGroupMode != string(LABEL) && *kubernetesServiceGroupMode != string(VOLUME) && *kubernetesServiceGroupMode != "" {
return fmt.Errorf(
"unexpected Value for Kubernetes Service Groupe Mode field. Possible values are: %v, %v, ''", string(LABEL), string(VOLUME),
)
}

if *build == string(BUILD_CONFIG) {
return fmt.Errorf("the build value %v is only supported for Openshift provider", string(BUILD_CONFIG))
}
}

return nil
}

func (k *Kompose) createDeployment(options ConvertOptions) bool {
if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
return *kubernetesProvider.Controller == string(DEPLOYMENT)
}
return false
}

func (k *Kompose) createDaemonSet(options ConvertOptions) bool {
if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
return *kubernetesProvider.Controller == string(DAEMONSET)
}
return false
}

func (k *Kompose) createReplicationController(options ConvertOptions) bool {
if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
return *kubernetesProvider.Controller == string(REPLICATION_CONTROLLER)
}
return false
}

func (k *Kompose) createChart(options ConvertOptions) bool {
if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
return kubernetesProvider.Chart
}
return false
}

func (k *Kompose) multiContainerMode(options ConvertOptions) bool {
if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
return kubernetesProvider.MultiContainerMode
}
return false
}

func (k *Kompose) serviceGroupMode(options ConvertOptions) string {
if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
return *kubernetesProvider.ServiceGroupMode
}
return ""
}

func (k *Kompose) serviceGroupName(options ConvertOptions) string {
if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
return kubernetesProvider.ServiceGroupName
}
return ""
}

func (k *Kompose) secretsAsFiles(options ConvertOptions) bool {
if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
return kubernetesProvider.SecretsAsFiles
}
return false
}

func (k *Kompose) createDeploymentConfig(options ConvertOptions) bool {
if _, ok := options.Provider.(Openshift); ok {
return true
}
return false
}

func (k *Kompose) insecureRepository(options ConvertOptions) bool {
if openshiftProvider, ok := options.Provider.(Openshift); ok {
return openshiftProvider.InsecureRepository
}
return false
}

func (k *Kompose) buildRepo(options ConvertOptions) string {
if openshiftProvider, ok := options.Provider.(Openshift); ok {
return openshiftProvider.BuildRepo
}
return ""
}

func (k *Kompose) buildBranch(options ConvertOptions) string {
if openshiftProvider, ok := options.Provider.(Openshift); ok {
return openshiftProvider.BuildRepo
}
return ""
}

func (k *Kompose) getProvider(options ConvertOptions) string {
if _, ok := options.Provider.(Openshift); ok {
return "openshift"
}
if _, ok := options.Provider.(Kubernetes); ok {
return "kubernetes"
}
return "kubernetes"
}

func (k *Kompose) getController(options ConvertOptions) string {
if kubernetesProvider, ok := options.Provider.(Kubernetes); ok {
return *kubernetesProvider.Controller
}
return ""
}
83 changes: 83 additions & 0 deletions client/convert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package client

import (
"fmt"
"testing"

"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
appsv1 "k8s.io/api/apps/v1"
)

func TestConvertError(t *testing.T) {
randomBuildValue := "random-build"
randomVolumeTypeValue := "random-volume-type"
randomKubernetesControllerValue := "random-controller"
randomKubernetesServiceGroupModeValue := "random-group-mode"
buildConfigValue := string(BUILD_CONFIG)
testCases := []struct {
options ConvertOptions
errorMessage string
}{
{
options: ConvertOptions{
Build: &randomBuildValue,
},
errorMessage: fmt.Sprintf("unexpected Value for Build field. Possible values are: %v, %v, and %v", string(LOCAL), string(BUILD_CONFIG), string(NONE)),
},
{
options: ConvertOptions{
VolumeType: &randomVolumeTypeValue,
},
errorMessage: fmt.Sprintf("unexpected Value for VolumeType field. Possible values are: %v, %v, %v, %v", string(PVC), string(EMPTYDIR), string(HOSTPATH), string(CONFIGMAP)),
},
{
options: ConvertOptions{
Provider: Kubernetes{
Controller: &randomKubernetesControllerValue,
},
},
errorMessage: fmt.Sprintf("unexpected Value for Kubernetes Controller field. Possible values are: %v, %v, and %v", string(DEPLOYMENT), string(DAEMONSET), string(REPLICATION_CONTROLLER)),
},
{
options: ConvertOptions{
Provider: Kubernetes{
ServiceGroupMode: &randomKubernetesServiceGroupModeValue,
},
},
errorMessage: fmt.Sprintf("unexpected Value for Kubernetes Service Groupe Mode field. Possible values are: %v, %v, ''", string(LABEL), string(VOLUME)),
},
{
options: ConvertOptions{
Provider: Kubernetes{},
Build: &buildConfigValue,
},
errorMessage: fmt.Sprintf("the build value %v is only supported for Openshift provider", string(BUILD_CONFIG)),
},
}

client, err := NewClient()
assert.Check(t, is.Equal(err, nil))
for _, tc := range testCases {
_, err := client.Convert(tc.options)

assert.Check(t, is.Equal(err.Error(), tc.errorMessage))
}
}

func TestConvertWithDefaultOptions(t *testing.T) {
client, err := NewClient(WithErrorOnWarning())
assert.Check(t, is.Equal(err, nil))
objects, err := client.Convert(ConvertOptions{
OutFile: "./testdata/generated/",
InputFiles: []string{
"./testdata/docker-compose.yaml",
},
})
assert.Check(t, is.Equal(err, nil))
for _, object := range objects {
if deployment, ok := object.(*appsv1.Deployment); ok {
assert.Check(t, is.Equal(int(*deployment.Spec.Replicas), 1))
}
}
}
25 changes: 25 additions & 0 deletions client/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package client

// Opt is a configuration option to initialize a client
type Opt func(*Kompose) error

func WithSuppressWarnings() Opt {
return func(k *Kompose) error {
k.suppressWarnings = true
return nil
}
}

func WithVerboseOutput() Opt {
return func(k *Kompose) error {
k.verbose = true
return nil
}
}

func WithErrorOnWarning() Opt {
return func(k *Kompose) error {
k.errorOnWarning = true
return nil
}
}
Loading

0 comments on commit ea80734

Please sign in to comment.