Skip to content

Commit

Permalink
feat: add support for Argo Rollouts (#1098)
Browse files Browse the repository at this point in the history
* feat: add support for Argo Rollouts
* fix: add untracked files
* fix: use argo rollouts library structs
* feat: add finer grained support for the type of rollout
* fix: resolve CI errors
* fix: resolve requested changes

Signed-off-by: Soumil Paranjpay <[email protected]>
  • Loading branch information
Soumil-07 authored Dec 13, 2023
1 parent 445020f commit 0bbafac
Show file tree
Hide file tree
Showing 10 changed files with 568 additions and 343 deletions.
6 changes: 4 additions & 2 deletions common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ const (
ConfigStoragesKey = BaseKey + d + "storages"
//ConfigMinReplicasKey represents Ingress host Key
ConfigMinReplicasKey = BaseKey + d + "minreplicas"
//ConfigStatefulSetKey represents whether the IR should generate a StatefulSet
ConfigStatefulSetKey = "statefulset"
//ConfigDeploymentTypeKey represents which type of Deployment should be generated
ConfigDeploymentTypeKey = "deployment"
//ConfigPortsForServiceKeySegment represents the ports used for service
ConfigPortsForServiceKeySegment = "ports"
//ConfigPortForServiceKeySegment represents the port used for service
Expand Down Expand Up @@ -203,6 +203,8 @@ const (
ConfigServicesChildModulesSpringProfilesKey = ConfigServicesKey + d + "%s" + d + "childModules" + d + "%s" + d + "springBootProfiles"
// ConfigTransformersKubernetesArgoCDNamespaceKey represents namespace key for argocd transformer
ConfigTransformersKubernetesArgoCDNamespaceKey = ConfigTransformersKey + d + "kubernetes" + d + "argocd" + d + "namespace"
// ConfigArgoRolloutTypeKey represents the type of Rollout that should be generated.
ConfigArgoRolloutTypeKey = "argorollout"
//VCSKey represents version control system key
VCSKey = BaseKey + d + "vcs"
//GitKey represents git qa key
Expand Down
215 changes: 113 additions & 102 deletions go.mod

Large diffs are not rendered by default.

541 changes: 319 additions & 222 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion transformer/kubernetes/apiresource/argocdapplication.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (*ArgoCDApplication) createNewResource(irApplication irtypes.Application, t
TypeMeta: metav1.TypeMeta{APIVersion: appGVK.GroupVersion().String(), Kind: appGVK.Kind},
ObjectMeta: metav1.ObjectMeta{Name: irApplication.Name, Namespace: argoCDNameSpace},
Spec: v1alpha1.ApplicationSpec{
Source: v1alpha1.ApplicationSource{
Source: &v1alpha1.ApplicationSource{
RepoURL: repoURL,
TargetRevision: repoRef,
Path: repoPath,
Expand Down
93 changes: 88 additions & 5 deletions transformer/kubernetes/apiresource/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package apiresource
import (
"strings"

argorollouts "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
"github.com/konveyor/move2kube/common"
"github.com/konveyor/move2kube/qaengine"
"github.com/konveyor/move2kube/transformer/kubernetes/k8sschema"
collecttypes "github.com/konveyor/move2kube/types/collection"
irtypes "github.com/konveyor/move2kube/types/ir"
Expand All @@ -28,9 +30,18 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
apps "k8s.io/kubernetes/pkg/apis/apps"
batch "k8s.io/kubernetes/pkg/apis/batch"
core "k8s.io/kubernetes/pkg/apis/core"
corev1conversions "k8s.io/kubernetes/pkg/apis/core/v1"
)

type rolloutType string

const (
blueGreenRollout rolloutType = "BlueGreen"
canaryRollout rolloutType = "Canary"
)

//TODO: Add support for replicaset, cronjob and statefulset
Expand All @@ -48,6 +59,7 @@ const (
daemonSetKind string = "DaemonSet"
// statefulSetKind defines StatefulSet Kind
statefulSetKind string = "StatefulSet"
rolloutKind string = "Rollout"
)

// Deployment handles all objects like a Deployment
Expand All @@ -56,7 +68,7 @@ type Deployment struct {

// getSupportedKinds returns kinds supported by the deployment
func (d *Deployment) getSupportedKinds() []string {
return []string{podKind, jobKind, common.DeploymentKind, deploymentConfigKind, replicationControllerKind, daemonSetKind, statefulSetKind}
return []string{podKind, jobKind, common.DeploymentKind, deploymentConfigKind, replicationControllerKind, daemonSetKind, statefulSetKind, rolloutKind}
}

// createNewResources converts ir to runtime object
Expand All @@ -69,11 +81,17 @@ func (d *Deployment) createNewResources(ir irtypes.EnhancedIR, supportedKinds []
logrus.Errorf("Creating Daemonset even though not supported by target cluster.")
}
obj = d.createDaemonSet(service, targetCluster.Spec)
} else if service.StatefulSet {
} else if service.DeploymentType == irtypes.DeploymentTypeStatefulSet {
if !common.IsPresent(supportedKinds, statefulSetKind) {
logrus.Errorf("Creating Statefulset even though not supported by target cluster.")
}
obj = d.createStatefulSet(service, targetCluster.Spec)
} else if service.DeploymentType == irtypes.DeploymentTypeArgoRollout {
var err error
obj, err = d.createArgorollout(service, targetCluster.Spec)
if err != nil {
logrus.Errorf("Error creating Argo Rollout: %v", err)
}
} else if service.RestartPolicy == core.RestartPolicyNever || service.RestartPolicy == core.RestartPolicyOnFailure {
if common.IsPresent(supportedKinds, jobKind) {
obj = d.createJob(service, targetCluster.Spec)
Expand Down Expand Up @@ -303,6 +321,71 @@ func (d *Deployment) createStatefulSet(service irtypes.Service, cluster collectt
return &statefulset
}

func (d *Deployment) createArgorollout(service irtypes.Service, cluster collecttypes.ClusterMetadataSpec) (*argorollouts.Rollout, error) {
podSpec := service.PodSpec
podSpec = irtypes.PodSpec(d.convertVolumesKindsByPolicy(core.PodSpec(podSpec), cluster))

meta := metav1.ObjectMeta{
Name: service.Name,
Labels: getPodLabels(service.Name, service.Networks),
Annotations: getAnnotations(service),
}
replicas := int32(service.Replicas)
var v1spec corev1.PodSpec
var corespec core.PodSpec = core.PodSpec(podSpec)
err := corev1conversions.Convert_core_PodSpec_To_v1_PodSpec(&corespec, &v1spec, nil)
if err != nil {
return nil, err
}

// prompt type of rollout
qaKey := common.JoinQASubKeys(common.ConfigServicesKey, common.ConfigDeploymentTypeKey, common.ConfigArgoRolloutTypeKey)
desc := "Which type of Argo rollout should be generated?"
def := string(blueGreenRollout)
options := []string{string(blueGreenRollout), string(canaryRollout)}
rolloutType := qaengine.FetchSelectAnswer(qaKey, desc, nil, def, options, nil)

var rolloutStrategy argorollouts.RolloutStrategy
if rolloutType == string(blueGreenRollout) {
rolloutStrategy = argorollouts.RolloutStrategy{
BlueGreen: &argorollouts.BlueGreenStrategy{
ActiveService: service.Name,
PreviewService: service.Name + "-preview",
AutoPromotionEnabled: func(b bool) *bool { return &b }(false),
},
}
} else {
rolloutStrategy = argorollouts.RolloutStrategy{
Canary: &argorollouts.CanaryStrategy{
StableService: service.Name,
CanaryService: service.Name + "-preview",
MaxSurge: &intstr.IntOrString{StrVal: "25%"},
},
}
}

rollout := argorollouts.Rollout{
TypeMeta: metav1.TypeMeta{
Kind: rolloutKind,
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: meta,
Spec: argorollouts.RolloutSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: getServiceLabels(meta.Name),
},
Template: corev1.PodTemplateSpec{
ObjectMeta: meta,
Spec: v1spec,
},
Strategy: rolloutStrategy,
},
}

return &rollout, nil
}

// Conversions section

func (d *Deployment) toDeploymentConfig(meta metav1.ObjectMeta, podspec core.PodSpec, replicas int32, cluster collecttypes.ClusterMetadataSpec) *okdappsv1.DeploymentConfig {
Expand Down Expand Up @@ -421,9 +504,9 @@ func (d *Deployment) toPod(meta metav1.ObjectMeta, podspec core.PodSpec, restart
return &pod
}

//Volumes and volume mounts of all containers are transformed as follows:
//1. Each container's volume mount list and corresponding volumes are transformed
//2. Unreferenced volumes are discarded
// Volumes and volume mounts of all containers are transformed as follows:
// 1. Each container's volume mount list and corresponding volumes are transformed
// 2. Unreferenced volumes are discarded
func (d *Deployment) convertVolumesKindsByPolicy(podspec core.PodSpec, cluster collecttypes.ClusterMetadataSpec) core.PodSpec {
if podspec.Volumes == nil || len(podspec.Volumes) == 0 {
return podspec
Expand Down
9 changes: 8 additions & 1 deletion transformer/kubernetes/apiresource/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ func (d *Service) createNewResources(ir irtypes.EnhancedIR, supportedKinds []str
}
obj := d.createService(service)
objs = append(objs, obj)
// for Argo Rollouts, 2 services are required: one for the stable version, and one
// for the experimental version
if service.DeploymentType == irtypes.DeploymentTypeArgoRollout {
previewSvc := d.createService(service)
previewSvc.ObjectMeta.Name = service.Name + "-preview"
objs = append(objs, previewSvc)
}
}

// Create one ingress for all services
Expand Down Expand Up @@ -474,7 +481,7 @@ func (d *Service) createService(service irtypes.Service) *core.Service {
Ports: ports,
},
}
if len(ports) == 0 || service.StatefulSet {
if len(ports) == 0 || service.DeploymentType == irtypes.DeploymentTypeStatefulSet {
svc.Spec.ClusterIP = "None"
}
return svc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (opt *ingressPreprocessor) preprocess(ir irtypes.IR, targetCluster collecti
portForwarding.ServiceRelPath = "/" + serviceName
}
// Create Headless Services for services that are to be converted into StatefulSets
if service.StatefulSet {
if service.DeploymentType == irtypes.DeploymentTypeStatefulSet {
portForwarding.ServiceType = core.ServiceTypeClusterIP
}
noneServiceType := "Don't create service"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ func (sp statefulsetPreprocessor) preprocess(ir irtypes.IR, targetCluster collec
}

for k, scObj := range ir.Services {
isStateful := commonqa.IsStateful(scObj.Name)
scObj.StatefulSet = isStateful
isStateful := commonqa.GetDeploymentType(scObj.Name)
scObj.DeploymentType = isStateful
ir.Services[k] = scObj
}

return ir, nil
}
}
16 changes: 14 additions & 2 deletions types/ir/ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ const (
RelDockerfileContextContainerBuildArtifactTypeValue ContainerBuildArtifactTypeValue = "RelDockerfileContextPath"
)

// DeploymentType represents the type of deployment artifact generated by Move2Kube
type DeploymentType string

const (
// DeploymentTypeDeployment represents a Kubernetes Deployment
DeploymentTypeDeployment DeploymentType = "Deployment"
// DeploymentTypeStatefulSet represents a Kubernetes StatefulSet
DeploymentTypeStatefulSet DeploymentType = "StatefulSet"
// DeploymentTypeArgoRollout represents an Argo CD Rollout
DeploymentTypeArgoRollout DeploymentType = "ArgoRollout"
)

// IRArtifactType represents artifact type of IR
const IRArtifactType transformertypes.ArtifactType = "IR"

Expand Down Expand Up @@ -113,8 +125,8 @@ type Service struct {
Replicas int
Networks []string
OnlyIngress bool
Daemon bool //Gets converted to DaemonSet
StatefulSet bool // Whether the service gets converted to a StatefulSet
Daemon bool //Gets converted to DaemonSet
DeploymentType DeploymentType // The type of Deployment this service gets converted to (Rollout/StatefulSet/Deployment)
}

// ServiceToPodPortForwarding forwards a k8s service port to a k8s pod port
Expand Down
21 changes: 17 additions & 4 deletions types/qaengine/commonqa/commonqa.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
dockercliconfig "github.com/docker/cli/cli/config"
"github.com/konveyor/move2kube/common"
"github.com/konveyor/move2kube/qaengine"
"github.com/konveyor/move2kube/types/ir"
qatypes "github.com/konveyor/move2kube/types/qaengine"
"github.com/sirupsen/logrus"
"github.com/spf13/cast"
Expand Down Expand Up @@ -152,8 +153,20 @@ func GetPortForService(detectedPorts []int32, qaSubKey string) int32 {
return int32(selectedPort)
}

// IsStateful returns whether the Service should generate a StatefulSet
func IsStateful(serviceName string) bool {
quesKey := common.JoinQASubKeys(common.ConfigServicesKey, serviceName, common.ConfigStatefulSetKey)
return qaengine.FetchBoolAnswer(quesKey, fmt.Sprintf("For the service '%s', do you require a StatefulSet instead of a Deployment?", serviceName), nil, false, nil)
// GetDeploymentType returns the type of Deployment the service should generate
func GetDeploymentType(serviceName string) ir.DeploymentType {
quesKey := common.JoinQASubKeys(common.ConfigServicesKey, serviceName, common.ConfigDeploymentTypeKey)
desc := fmt.Sprintf("For the service %s, which type of deployment is required?", serviceName)
def := string(ir.DeploymentTypeDeployment)
options := []string{string(ir.DeploymentTypeDeployment), string(ir.DeploymentTypeStatefulSet), string(ir.DeploymentTypeArgoRollout)}
deplType := qaengine.FetchSelectAnswer(quesKey, desc, nil, def, options, nil)

switch deplType {
case string(ir.DeploymentTypeDeployment):
return ir.DeploymentTypeDeployment
case string(ir.DeploymentTypeStatefulSet):
return ir.DeploymentTypeStatefulSet
default:
return ir.DeploymentTypeArgoRollout
}
}

0 comments on commit 0bbafac

Please sign in to comment.