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 }