github.com/docker/compose-on-kubernetes@v0.5.0/internal/registry/strategyvalidate.go (about)

     1  package registry
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/docker/compose-on-kubernetes/internal/stackresources"
     9  
    10  	composelabels "github.com/docker/compose-on-kubernetes/api/labels"
    11  	"github.com/docker/compose-on-kubernetes/internal/conversions"
    12  	"github.com/docker/compose-on-kubernetes/internal/convert"
    13  	iv "github.com/docker/compose-on-kubernetes/internal/internalversion"
    14  	"github.com/pkg/errors"
    15  	log "github.com/sirupsen/logrus"
    16  	coretypes "k8s.io/api/core/v1"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/util/validation"
    19  	"k8s.io/apimachinery/pkg/util/validation/field"
    20  	appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
    21  	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    22  )
    23  
    24  // ValidateCollisions against existing objects
    25  func ValidateCollisions(coreClient corev1.ServicesGetter, appsClient appsv1.AppsV1Interface, stack *iv.Stack) field.ErrorList {
    26  	return validateCollisions(coreClient, appsClient)(nil, stack)
    27  }
    28  func validateCollisions(coreClient corev1.ServicesGetter, appsClient appsv1.AppsV1Interface) validateStep {
    29  	return func(_ context.Context, stack *iv.Stack) field.ErrorList {
    30  		if stack.Spec.Stack == nil {
    31  			return field.ErrorList{}
    32  		}
    33  
    34  		stackDef, err := convertToStackDefinition(stack)
    35  		if err != nil {
    36  			// something was wrong elsewhere in the validation chain
    37  			return field.ErrorList{}
    38  		}
    39  
    40  		var errs field.ErrorList
    41  		for _, v := range stackDef.Services {
    42  			svc, err := coreClient.Services(stack.Namespace).Get(v.Name, metav1.GetOptions{})
    43  			if err == nil {
    44  				errs = appendErrOnCollision(svc.ObjectMeta.Labels, "service", v.Name, stack.Name, errs)
    45  			}
    46  		}
    47  		for _, v := range stackDef.Deployments {
    48  			dep, err := appsClient.Deployments(stack.Namespace).Get(v.Name, metav1.GetOptions{})
    49  			if err == nil {
    50  				errs = appendErrOnCollision(dep.ObjectMeta.Labels, "deployment", v.Name, stack.Name, errs)
    51  			}
    52  		}
    53  		for _, v := range stackDef.Statefulsets {
    54  			ss, err := appsClient.StatefulSets(stack.Namespace).Get(v.Name, metav1.GetOptions{})
    55  			if err == nil {
    56  				errs = appendErrOnCollision(ss.ObjectMeta.Labels, "statefulset", v.Name, stack.Name, errs)
    57  			}
    58  		}
    59  		for _, v := range stackDef.Daemonsets {
    60  			ds, err := appsClient.DaemonSets(stack.Namespace).Get(v.Name, metav1.GetOptions{})
    61  			if err == nil {
    62  				errs = appendErrOnCollision(ds.ObjectMeta.Labels, "daemonset", v.Name, stack.Name, errs)
    63  			}
    64  		}
    65  		return errs
    66  	}
    67  }
    68  
    69  func appendErrOnCollision(labels map[string]string, kind string, name string, stackName string, errs field.ErrorList) field.ErrorList {
    70  	res := errs
    71  	if key, ok := labels[composelabels.ForStackName]; ok {
    72  		if key != stackName {
    73  			res = append(res, field.Duplicate(field.NewPath(stackName), fmt.Sprintf("%s %s already exists in stack %s", kind, name, key)))
    74  		}
    75  	} else {
    76  		res = append(res, field.Duplicate(field.NewPath(stackName), fmt.Sprintf("%s %s already exists", kind, name)))
    77  	}
    78  	return res
    79  }
    80  
    81  // ValidateObjectNames validates object names
    82  func ValidateObjectNames(stack *iv.Stack) field.ErrorList {
    83  	return validateObjectNames()(nil, stack)
    84  }
    85  func validateObjectNames() validateStep {
    86  	return func(_ context.Context, stack *iv.Stack) field.ErrorList {
    87  		if stack == nil || stack.Spec.Stack == nil {
    88  			return nil
    89  		}
    90  		errs := field.ErrorList{}
    91  		for ix, svc := range stack.Spec.Stack.Services {
    92  			result := validation.IsDNS1123Subdomain(svc.Name)
    93  			if len(result) > 0 {
    94  				errs = append(errs, field.Invalid(field.NewPath("spec", "stack", "services").Index(ix).Child("name"),
    95  					svc.Name,
    96  					"not a valid service name in Kubernetes: "+strings.Join(result, ", ")))
    97  			}
    98  			for i, volume := range svc.Volumes {
    99  				// FIXME(vdemeester) deduplicate this with internal/convert
   100  				volumename := fmt.Sprintf("mount-%d", i)
   101  				if volume.Type == "volume" && volume.Source != "" {
   102  					volumename = volume.Source
   103  				}
   104  				result = validation.IsDNS1123Subdomain(volumename)
   105  				if len(result) > 0 {
   106  					errs = append(errs, field.Invalid(field.NewPath("spec", "stack", "services").Index(ix).Child("volumes").Index(i),
   107  						volumename,
   108  						"not a valid volume name in Kubernetes: "+strings.Join(result, ", ")))
   109  				}
   110  			}
   111  		}
   112  		for secret := range stack.Spec.Stack.Secrets {
   113  			result := validation.IsDNS1123Subdomain(secret)
   114  			if len(result) > 0 {
   115  				errs = append(errs, field.Invalid(field.NewPath("spec", "stack", "secrets").Child("secret"),
   116  					secret,
   117  					"not a valid secret name in Kubernetes: "+strings.Join(result, ", ")))
   118  			}
   119  		}
   120  		return errs
   121  	}
   122  }
   123  
   124  // ValidateDryRun validates that conversion to k8s objects works well
   125  func ValidateDryRun(stack *iv.Stack) field.ErrorList {
   126  	return validateDryRun()(nil, stack)
   127  }
   128  func validateDryRun() validateStep {
   129  	return func(_ context.Context, stack *iv.Stack) field.ErrorList {
   130  		if _, err := convertToStackDefinition(stack); err != nil {
   131  			return field.ErrorList{
   132  				field.Invalid(field.NewPath(stack.Name), nil, err.Error()),
   133  			}
   134  		}
   135  		return nil
   136  	}
   137  }
   138  
   139  func convertToStackDefinition(stack *iv.Stack) (*stackresources.StackState, error) {
   140  	stackLatest, err := conversions.StackFromInternalV1alpha3(stack)
   141  	if err != nil {
   142  		return nil, errors.Wrap(err, "conversion to v1alpha3 failed")
   143  	}
   144  	strategy, err := convert.ServiceStrategyFor(coretypes.ServiceTypeLoadBalancer) // in that case, service strategy does not really matter
   145  	if err != nil {
   146  		log.Errorf("Failed to convert to stack: %s", err)
   147  		if err != nil {
   148  			return nil, errors.Wrap(err, "conversion to kube entities failed")
   149  		}
   150  	}
   151  	sd, err := convert.StackToStack(*stackLatest, strategy, stackresources.EmptyStackState)
   152  	if err != nil {
   153  		log.Errorf("Failed to convert to stack: %s", err)
   154  		if err != nil {
   155  			return nil, errors.Wrap(err, "conversion to kube entities failed")
   156  		}
   157  	}
   158  	return sd, nil
   159  }
   160  
   161  func validateCreationStatus() validateStep {
   162  	return func(_ context.Context, stack *iv.Stack) field.ErrorList {
   163  		if stack.Status != nil && stack.Status.Phase == iv.StackFailure {
   164  			return field.ErrorList{
   165  				field.Invalid(field.NewPath(stack.Name), nil, stack.Status.Message),
   166  			}
   167  		}
   168  		return nil
   169  	}
   170  }
   171  
   172  func validateStackNotNil() validateStep {
   173  	return func(_ context.Context, stack *iv.Stack) field.ErrorList {
   174  		if stack.Spec.Stack == nil {
   175  			// in this case, the status should have been filled with error message
   176  			msg := "stack is empty"
   177  			if stack.Status != nil {
   178  				msg = stack.Status.Message
   179  			}
   180  			return field.ErrorList{
   181  				field.Invalid(field.NewPath(stack.Name), nil, msg),
   182  			}
   183  		}
   184  		return nil
   185  	}
   186  }