Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/mapt/cmd/aws/services/snc.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const (
disableClusterReadinessDesc = "If this flag is set it will skip the checks for the cluster readiness. In this case the kubeconfig can not be generated"

sncProfile = "profile"
sncProfileDesc = "comma separated list of profiles to apply on the SNC cluster. Profiles available: virtualization"
sncProfileDesc = "comma separated list of profiles to apply on the SNC cluster. Profiles available: virtualization, servicemesh"
)

func GetOpenshiftSNCCmd() *cobra.Command {
Expand Down
6 changes: 6 additions & 0 deletions pkg/provider/aws/action/snc/snc.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,12 @@ func (r *openshiftSNCRequest) deploy(ctx *pulumi.Context) error {
}
ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, apiSNC.OutputKubeconfig),
pulumi.ToSecret(kubeconfig))
// Write kubeconfig to disk early so it is available even if profile deployment fails
if outputPath := r.mCtx.GetResultsOutputPath(); len(outputPath) > 0 {
kubeconfig.ApplyT(func(kc string) error {
return os.WriteFile(fmt.Sprintf("%s/kubeconfig", outputPath), []byte(kc), 0600)
})
}
// Deploy profiles using Kubernetes provider
if len(r.profiles) > 0 {
k8sProvider, err := apiSNC.NewK8sProvider(ctx, "k8s-provider", kubeconfig)
Expand Down
12 changes: 10 additions & 2 deletions pkg/target/service/snc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,22 @@ func waitForCRCondition(ctx context.Context, kubeconfig string, gvr schema.Group
}

// findResource returns a single resource by exact name or by name prefix.
// When namespace is empty, the resource is looked up at cluster scope.
func findResource(ctx context.Context, dc dynamic.Interface, gvr schema.GroupVersionResource,
namespace, name string, prefixMatch bool) (*unstructured.Unstructured, error) {

var ri dynamic.ResourceInterface
if namespace == "" {
ri = dc.Resource(gvr)
} else {
ri = dc.Resource(gvr).Namespace(namespace)
}

if !prefixMatch {
return dc.Resource(gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
return ri.Get(ctx, name, metav1.GetOptions{})
}

list, err := dc.Resource(gvr).Namespace(namespace).List(ctx, metav1.ListOptions{})
list, err := ri.List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
Expand Down
181 changes: 181 additions & 0 deletions pkg/target/service/snc/profile_servicemesh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package snc

import (
"fmt"
"time"

"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apiextensions"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"k8s.io/apimachinery/pkg/runtime/schema"
)

const (
istioSystemNamespace = "istio-system"
istioCNINamespace = "istio-cni"
)

var (
sailCSVGVR = schema.GroupVersionResource{
Group: "operators.coreos.com",
Version: "v1alpha1",
Resource: "clusterserviceversions",
}
istioGVR = schema.GroupVersionResource{
Group: "sailoperator.io",
Version: "v1",
Resource: "istios",
}
istioCNIGVR = schema.GroupVersionResource{
Group: "sailoperator.io",
Version: "v1",
Resource: "istiocnis",
}
)

func deployServiceMesh(ctx *pulumi.Context, args *ProfileDeployArgs) (pulumi.Resource, error) {
goCtx := ctx.Context()
rn := func(suffix string) string {
return fmt.Sprintf("%s-smesh-%s", args.Prefix, suffix)
}

// Create istio-system namespace
nsSystem, err := corev1.NewNamespace(ctx, rn("ns-system"),
&corev1.NamespaceArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String(istioSystemNamespace),
},
},
pulumi.Provider(args.K8sProvider),
pulumi.DependsOn(args.Deps))
if err != nil {
return nil, err
}

// Create istio-cni namespace
nsCNI, err := corev1.NewNamespace(ctx, rn("ns-cni"),
&corev1.NamespaceArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String(istioCNINamespace),
},
},
pulumi.Provider(args.K8sProvider),
pulumi.DependsOn(args.Deps))
if err != nil {
return nil, err
}

// Create Subscription for the OpenShift Service Mesh 3 operator
sub, err := apiextensions.NewCustomResource(ctx, rn("sub"),
&apiextensions.CustomResourceArgs{
ApiVersion: pulumi.String("operators.coreos.com/v1alpha1"),
Kind: pulumi.String("Subscription"),
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("servicemeshoperator3"),
Namespace: pulumi.String("openshift-operators"),
},
OtherFields: map[string]interface{}{
"spec": map[string]interface{}{
"source": "redhat-operators",
"sourceNamespace": "openshift-marketplace",
"name": "servicemeshoperator3",
"channel": "stable",
"installPlanApproval": "Automatic",
},
},
},
pulumi.Provider(args.K8sProvider),
pulumi.DependsOn([]pulumi.Resource{nsSystem, nsCNI}))
if err != nil {
return nil, err
}

// Wait for the Service Mesh operator CSV to succeed
csvReady := pulumi.All(sub.ID(), args.Kubeconfig).ApplyT(
func(allArgs []interface{}) (string, error) {
kc := allArgs[1].(string)
if err := waitForCRCondition(goCtx, kc, sailCSVGVR,
"openshift-operators", "servicemeshoperator3",
"", "Succeeded", 20*time.Minute, true); err != nil {
return "", fmt.Errorf("waiting for Service Mesh operator CSV: %w", err)
}
return "ready", nil
}).(pulumi.StringOutput)

// Create IstioCNI CR
istioCNIName := csvReady.ApplyT(func(_ string) string {
return "default"
}).(pulumi.StringOutput)

// IstioCNI is cluster-scoped
cni, err := apiextensions.NewCustomResource(ctx, rn("istiocni"),
&apiextensions.CustomResourceArgs{
ApiVersion: pulumi.String("sailoperator.io/v1"),
Kind: pulumi.String("IstioCNI"),
Metadata: &metav1.ObjectMetaArgs{
Name: istioCNIName,
},
OtherFields: map[string]interface{}{
"spec": map[string]interface{}{
"namespace": istioCNINamespace,
"profile": "openshift",
},
},
},
pulumi.Provider(args.K8sProvider))
if err != nil {
return nil, err
}

// Wait for IstioCNI to be ready (cluster-scoped, empty namespace)
cniReady := pulumi.All(cni.ID(), args.Kubeconfig).ApplyT(
func(allArgs []interface{}) (string, error) {
kc := allArgs[1].(string)
if err := waitForCRCondition(goCtx, kc, istioCNIGVR,
"", "default",
"Ready", "True", 20*time.Minute, false); err != nil {
return "", fmt.Errorf("waiting for IstioCNI: %w", err)
}
return "ready", nil
}).(pulumi.StringOutput)

// Create Istio CR (cluster-scoped, depends on CNI being ready)
istioName := cniReady.ApplyT(func(_ string) string {
return "default"
}).(pulumi.StringOutput)

istio, err := apiextensions.NewCustomResource(ctx, rn("istio"),
&apiextensions.CustomResourceArgs{
ApiVersion: pulumi.String("sailoperator.io/v1"),
Kind: pulumi.String("Istio"),
Metadata: &metav1.ObjectMetaArgs{
Name: istioName,
},
OtherFields: map[string]interface{}{
"spec": map[string]interface{}{
"namespace": istioSystemNamespace,
},
},
},
pulumi.Provider(args.K8sProvider))
if err != nil {
return nil, err
}

// Wait for Istio to be ready (cluster-scoped, empty namespace)
istioReady := pulumi.All(istio.ID(), args.Kubeconfig).ApplyT(
func(allArgs []interface{}) (string, error) {
kc := allArgs[1].(string)
if err := waitForCRCondition(goCtx, kc, istioGVR,
"", "default",
"Ready", "True", 20*time.Minute, false); err != nil {
return "", fmt.Errorf("waiting for Istio: %w", err)
}
return "ready", nil
}).(pulumi.StringOutput)

ctx.Export("istioReady", istioReady)

return istio, nil
}
5 changes: 4 additions & 1 deletion pkg/target/service/snc/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import (

const (
ProfileVirtualization = "virtualization"
ProfileServiceMesh = "servicemesh"
)

// validProfiles is the single source of truth for supported profile names.
var validProfiles = []string{ProfileVirtualization}
var validProfiles = []string{ProfileVirtualization, ProfileServiceMesh}

// ProfileDeployArgs holds the arguments needed by a profile to deploy
// its resources on the SNC cluster.
Expand All @@ -40,6 +41,8 @@ func DeployProfile(ctx *pulumi.Context, profile string, args *ProfileDeployArgs)
switch profile {
case ProfileVirtualization:
return deployVirtualization(ctx, args)
case ProfileServiceMesh:
return deployServiceMesh(ctx, args)
default:
return nil, fmt.Errorf("profile %q has no deploy function", profile)
}
Expand Down