diff --git a/apis/project.cattle.io/v3/schema/endpoint_address.go b/apis/project.cattle.io/v3/schema/endpoint_address.go new file mode 100644 index 00000000..608da81d --- /dev/null +++ b/apis/project.cattle.io/v3/schema/endpoint_address.go @@ -0,0 +1,100 @@ +package schema + +import ( + "github.com/rancher/norman/types" + "github.com/rancher/norman/types/convert" + "github.com/rancher/norman/types/mapper" + "k8s.io/api/core/v1" +) + +type EndpointAddressMapper struct { +} + +func (e EndpointAddressMapper) FromInternal(data map[string]interface{}) { + if data == nil { + return + } + + var subsets []v1.EndpointSubset + if err := convert.ToObj(data["subsets"], &subsets); err != nil { + log.Errorf("Failed to convert subset: %v", err) + return + } + + var noPortsIPs []string + var result []interface{} + for _, subset := range subsets { + var ips []string + for _, ip := range subset.Addresses { + if ip.IP != "" { + ips = append(ips, ip.IP) + } + if ip.Hostname != "" { + ips = append(ips, ip.Hostname) + } + } + + if len(ips) == 0 { + noPortsIPs = append(noPortsIPs, ips...) + } else { + for _, port := range subset.Ports { + if len(ips) > 0 { + result = append(result, map[string]interface{}{ + "addresses": ips, + "port": port.Port, + "protocol": port.Protocol, + }) + } + } + } + } + + if len(noPortsIPs) > 0 { + result = append(result, map[string]interface{}{ + "addresses": noPortsIPs, + }) + } + + if len(result) > 0 { + data["targets"] = result + } +} + +func (e EndpointAddressMapper) ToInternal(data map[string]interface{}) { + if data == nil { + return + } + + var addresses []Target + var subsets []v1.EndpointSubset + if err := convert.ToObj(data["targets"], &addresses); err != nil { + log.Errorf("Failed to convert addresses: %v", err) + return + } + + for _, address := range addresses { + subset := v1.EndpointSubset{} + for _, ip := range address.Addresses { + subset.Addresses = append(subset.Addresses, v1.EndpointAddress{ + IP: ip, + }) + } + if address.Port != nil { + subset.Ports = append(subset.Ports, v1.EndpointPort{ + Port: *address.Port, + Protocol: v1.Protocol(address.Protocol), + }) + } + subsets = append(subsets, subset) + } + + if len(subsets) > 0 { + data["subsets"] = subsets + } +} + +func (e EndpointAddressMapper) ModifySchema(schema *types.Schema, schemas *types.Schemas) error { + mapper.ValidateField("subsets", schema) + delete(schema.ResourceFields, "subsets") + return nil +} diff --git a/apis/project.cattle.io/v3/schema/log.go b/apis/project.cattle.io/v3/schema/log.go new file mode 100644 index 00000000..91e27c15 --- /dev/null +++ b/apis/project.cattle.io/v3/schema/log.go @@ -0,0 +1,7 @@ +package schema + +import "github.com/sirupsen/logrus" + +var ( + log = logrus.WithField("component", "types/mapper") +) diff --git a/apis/project.cattle.io/v3/schema/registry_credential.go b/apis/project.cattle.io/v3/schema/registry_credential.go new file mode 100644 index 00000000..809a2d33 --- /dev/null +++ b/apis/project.cattle.io/v3/schema/registry_credential.go @@ -0,0 +1,32 @@ +package schema + +import ( + "encoding/base64" + + "github.com/rancher/norman/types" + "github.com/rancher/norman/types/convert" +) + +type RegistryCredentialMapper struct { +} + +func (e RegistryCredentialMapper) FromInternal(data map[string]interface{}) { +} + +func (e RegistryCredentialMapper) ToInternal(data map[string]interface{}) { + if data == nil { + return + } + + auth := convert.ToString(data["auth"]) + username := convert.ToString(data["username"]) + password := convert.ToString(data["password"]) + + if auth == "" && username != "" && password != "" { + data["auth"] = base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) + } +} + +func (e RegistryCredentialMapper) ModifySchema(schema *types.Schema, schemas *types.Schemas) error { + return nil +} diff --git a/apis/project.cattle.io/v3/schema/schema.go b/apis/project.cattle.io/v3/schema/schema.go index c05b4452..12fdd0d3 100644 --- a/apis/project.cattle.io/v3/schema/schema.go +++ b/apis/project.cattle.io/v3/schema/schema.go @@ -22,6 +22,8 @@ var ( Schemas = factory.Schemas(&Version). // Namespace must be first + Init(secretTypes). + Init(serviceTypes). Init(namespaceTypes). Init(podTypes). Init(deploymentTypes). @@ -84,19 +86,19 @@ func statefulSetTypes(schemas *types.Schemas) *types.Schemas { To: "deploymentStrategy/orderedConfig/partition", }, m.SetValue{ - From: "updateStrategy/type", + Field: "updateStrategy/type", IfEq: "OnDelete", Value: true, To: "deploymentStrategy/orderedConfig/onDelete", }, m.SetValue{ - From: "podManagementPolicy", + Field: "podManagementPolicy", IfEq: "Parallel", Value: "Parallel", To: "deploymentStrategy/kind", }, m.SetValue{ - From: "podManagementPolicy", + Field: "podManagementPolicy", IfEq: "OrderedReady", Value: "Ordered", To: "deploymentStrategy/kind", @@ -157,7 +159,7 @@ func daemonSet(schemas *types.Schemas) *types.Schemas { return schemas. AddMapperForType(&Version, v1beta2.DaemonSetSpec{}, m.SetValue{ - From: "updateStrategy/type", + Field: "updateStrategy/type", IfEq: "OnDelete", Value: true, To: "deploymentStrategy/globalConfig/onDelete", @@ -263,6 +265,7 @@ func podTypes(schemas *types.Schemas) *types.Schemas { mapper.NamespaceMapper{}, mapper.InitContainerMapper{}, mapper.SchedulingMapper{}, + m.Move{From: "tolerations", To: "scheduling/tolerations", DestDefined: true}, &m.Embed{Field: "securityContext"}, &m.Drop{Field: "serviceAccount"}, &m.SliceToMap{ @@ -304,3 +307,37 @@ func podTypes(schemas *types.Schemas) *types.Schemas { WorkloadID string `norman:"type=reference[workload]"` }{}) } + +func serviceTypes(schemas *types.Schemas) *types.Schemas { + return schemas. + TypeName("endpoint", v1.Endpoints{}). + AddMapperForType(&Version, v1.ServiceSpec{}, + &m.Move{From: "type", To: "serviceKind"}, + &m.Move{From: "externalName", To: "hostname"}, + &m.Move{From: "clusterIP", To: "clusterIp"}, + ServiceKindMapper{}, + ). + AddMapperForType(&Version, v1.Service{}, + &m.LabelField{Field: "workloadId"}, + &m.Drop{Field: "status"}, + &m.Move{From: "serviceKind", To: "kind"}, + &mapper.NamespaceIDMapper{}, + &m.AnnotationField{Field: "targetWorkloadIds", Object: true}, + &m.AnnotationField{Field: "targetServiceIds", Object: true}, + ). + AddMapperForType(&Version, v1.Endpoints{}, + &EndpointAddressMapper{}, + &mapper.NamespaceIDMapper{}, + ). + MustImport(&Version, v1.Service{}, projectOverride{}, struct { + WorkloadID string `json:"workloadId" norman:"type=reference[workload]"` + TargetWorkloadIDs string `json:"targetWorkloadIds" norman:"type=array[reference[workload]]"` + TargetServiceIDs string `json:"targetServiceIds" norman:"type=array[reference[service]]"` + Kind string `json:"kind" norman:"type=enum,options=Alias|ARecord|CName|ClusterIP|NodeIP|LoadBalancer"` + }{}). + MustImportAndCustomize(&Version, v1.Endpoints{}, func(schema *types.Schema) { + schema.CodeName = "Endpoint" + }, projectOverride{}, struct { + Targets []Target `json:"targets"` + }{}) +} diff --git a/apis/project.cattle.io/v3/schema/schema_secrets.go b/apis/project.cattle.io/v3/schema/schema_secrets.go new file mode 100644 index 00000000..00cc72e1 --- /dev/null +++ b/apis/project.cattle.io/v3/schema/schema_secrets.go @@ -0,0 +1,255 @@ +package schema + +import ( + "github.com/rancher/norman/types" + m "github.com/rancher/norman/types/mapper" + "github.com/rancher/types/apis/project.cattle.io/v3" + "github.com/rancher/types/mapper" + "k8s.io/api/core/v1" +) + +func secretTypes(schemas *types.Schemas) *types.Schemas { + return schemas. + AddMapperForType(&Version, v1.Secret{}, + m.SetValue{ + Field: "type", + To: "type", + IfEq: "kubernetes.io/service-account-token", + Value: "serviceAccountToken", + }, + m.SetValue{ + Field: "type", + To: "type", + IfEq: "kubernetes.io/dockercfg", + Value: "dockerCredential", + }, + m.SetValue{ + Field: "type", + To: "type", + IfEq: "kubernetes.io/dockerconfigjson", + Value: "dockerCredential", + }, + m.SetValue{ + Field: "type", + To: "type", + IfEq: "kubernetes.io/basic-auth", + Value: "basicAuth", + }, + m.SetValue{ + Field: "type", + To: "type", + IfEq: "kubernetes.io/ssh-auth", + Value: "sshAuth", + }, + m.SetValue{ + Field: "type", + To: "type", + IfEq: "kubernetes.io/ssh-auth", + Value: "sshAuth", + }, + m.SetValue{ + Field: "type", + To: "type", + IfEq: "kubernetes.io/tls", + Value: "certificate", + }, + &m.Move{From: "type", To: "kind"}, + &mapper.NamespaceIDMapper{}, + m.Condition{ + Field: "kind", + Value: "sshAuth", + Mapper: types.Mappers{ + m.UntypedMove{ + From: "data/ssh-privatekey", + To: "privateKey", + }, + m.Base64{ + Field: "privateKey", + IgnoreDefinition: true, + }, + m.SetValue{ + Field: "type", + Value: "sshAuth", + IgnoreDefinition: true, + }, + }, + }, + m.Condition{ + Field: "kind", + Value: "basicAuth", + Mapper: types.Mappers{ + m.UntypedMove{ + From: "data/username", + To: "username", + }, + m.UntypedMove{ + From: "data/password", + To: "password", + }, + m.Base64{ + Field: "username", + IgnoreDefinition: true, + }, + m.Base64{ + Field: "password", + IgnoreDefinition: true, + }, + m.SetValue{ + Field: "type", + Value: "basicAuth", + IgnoreDefinition: true, + }, + }, + }, + m.Condition{ + Field: "kind", + Value: "certificate", + Mapper: types.Mappers{ + m.UntypedMove{ + From: "data/tls.crt", + To: "certs", + }, + m.UntypedMove{ + From: "data/tls.key", + To: "key", + }, + m.Base64{ + Field: "certs", + IgnoreDefinition: true, + }, + m.Base64{ + Field: "key", + IgnoreDefinition: true, + }, + m.AnnotationField{Field: "certFingerprint", IgnoreDefinition: true}, + m.AnnotationField{Field: "cn", IgnoreDefinition: true}, + m.AnnotationField{Field: "version", IgnoreDefinition: true}, + m.AnnotationField{Field: "issuer", IgnoreDefinition: true}, + m.AnnotationField{Field: "issuedAt", IgnoreDefinition: true}, + m.AnnotationField{Field: "algorithm", IgnoreDefinition: true}, + m.AnnotationField{Field: "serialNumber", IgnoreDefinition: true}, + m.AnnotationField{Field: "keySize", IgnoreDefinition: true}, + m.AnnotationField{Field: "subjectAlternativeNames", IgnoreDefinition: true}, + m.SetValue{ + Field: "type", + Value: "certificate", + IgnoreDefinition: true, + }, + }, + }, + m.Condition{ + Field: "kind", + Value: "dockerCredential", + Mapper: types.Mappers{ + m.Base64{ + Field: "data/.dockercfg", + IgnoreDefinition: true, + }, + m.JSONEncode{ + Field: "data/.dockercfg", + IgnoreDefinition: true, + }, + m.UntypedMove{ + From: "data/.dockercfg", + To: "registries", + }, + m.Base64{ + Field: "data/.dockerconfigjson", + IgnoreDefinition: true, + }, + m.JSONEncode{ + Field: "data/.dockerconfigjson", + IgnoreDefinition: true, + }, + m.UntypedMove{ + From: "data/.dockerconfigjson/auths", + To: "registries", + }, + m.SetValue{ + Field: "type", + Value: "dockerCredential", + IgnoreDefinition: true, + }, + }, + }, + m.Condition{ + Field: "kind", + Value: "serviceAccountToken", + Mapper: types.Mappers{ + m.UntypedMove{ + From: "annotations!kubernetes.io/service-account.name", + To: "accountName", + Separator: "!", + }, + m.UntypedMove{ + From: "annotations!kubernetes.io/service-account.uid", + To: "accountUid", + Separator: "!", + }, + m.UntypedMove{ + From: "data/ca.crt", + To: "caCrt", + }, + m.UntypedMove{ + From: "data/namespace", + To: "namespace", + }, + m.UntypedMove{ + From: "data/token", + To: "token", + }, + m.Base64{ + Field: "caCrt", + IgnoreDefinition: true, + }, + m.Base64{ + Field: "namespace", + IgnoreDefinition: true, + }, + m.Base64{ + Field: "token", + IgnoreDefinition: true, + }, + m.SetValue{ + Field: "type", + Value: "serviceAccountToken", + IgnoreDefinition: true, + }, + }, + }, + ). + AddMapperForType(&Version, v3.RegistryCredential{}, RegistryCredentialMapper{}). + MustImportAndCustomize(&Version, v1.Secret{}, func(schema *types.Schema) { + schema.MustCustomizeField("kind", func(f types.Field) types.Field { + f.Options = []string{ + "Opaque", + "serviceAccountToken", + "dockerCredential", + "basicAuth", + "sshAuth", + "certificate", + } + return f + }) + }, projectOverride{}). + MustImportAndCustomize(&Version, v3.ServiceAccountToken{}, func(schema *types.Schema) { + schema.BaseType = "secret" + schema.Mapper = schemas.Schema(&Version, "secret").Mapper + }, projectOverride{}). + MustImportAndCustomize(&Version, v3.DockerCredential{}, func(schema *types.Schema) { + schema.BaseType = "secret" + schema.Mapper = schemas.Schema(&Version, "secret").Mapper + }, projectOverride{}). + MustImportAndCustomize(&Version, v3.Certificate{}, func(schema *types.Schema) { + schema.BaseType = "secret" + schema.Mapper = schemas.Schema(&Version, "secret").Mapper + }, projectOverride{}). + MustImportAndCustomize(&Version, v3.BasicAuth{}, func(schema *types.Schema) { + schema.BaseType = "secret" + schema.Mapper = schemas.Schema(&Version, "secret").Mapper + }, projectOverride{}). + MustImportAndCustomize(&Version, v3.SSHAuth{}, func(schema *types.Schema) { + schema.BaseType = "secret" + schema.Mapper = schemas.Schema(&Version, "secret").Mapper + }, projectOverride{}) +} diff --git a/apis/project.cattle.io/v3/schema/service_kind.go b/apis/project.cattle.io/v3/schema/service_kind.go new file mode 100644 index 00000000..4fecda5e --- /dev/null +++ b/apis/project.cattle.io/v3/schema/service_kind.go @@ -0,0 +1,54 @@ +package schema + +import ( + "github.com/rancher/norman/types" + "github.com/rancher/norman/types/convert" +) + +type ServiceKindMapper struct { +} + +func (s ServiceKindMapper) FromInternal(data map[string]interface{}) { + if data == nil { + return + } + + targetWorkloadIds := data["targetWorkloadIds"] + targetServiceIds := data["targetServiceIds"] + clusterIP := data["clusterIp"] + hostname := data["hostname"] + + if !convert.IsEmpty(targetWorkloadIds) || !convert.IsEmpty(targetServiceIds) { + data["serviceKind"] = "Alias" + } else if !convert.IsEmpty(hostname) { + data["serviceKind"] = "CName" + } else if clusterIP == "None" { + data["serviceKind"] = "ARecord" + } +} + +func (s ServiceKindMapper) ToInternal(data map[string]interface{}) { + if data == nil { + return + } + + str := convert.ToString(data["serviceKind"]) + switch str { + case "Alias": + fallthrough + case "ARecord": + fallthrough + case "CName": + data["serviceKind"] = "ClusterIP" + data["clusterIp"] = "None" + } + + if !convert.IsEmpty(data["hostname"]) { + data["kind"] = "ExternalName" + data["clusterIp"] = "None" + } +} + +func (s ServiceKindMapper) ModifySchema(schema *types.Schema, schemas *types.Schemas) error { + return nil +} diff --git a/apis/project.cattle.io/v3/types.go b/apis/project.cattle.io/v3/types.go index 1a9cbee1..9b70cdcc 100644 --- a/apis/project.cattle.io/v3/types.go +++ b/apis/project.cattle.io/v3/types.go @@ -60,3 +60,59 @@ type Link struct { Name string `json:"name"` Alias string `json:"alias"` } + +type ServiceAccountToken struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + AccountName string `json:"accountName"` + AccountUID string `json:"accountUid"` + Token string `json:"token" norman:"writeOnly"` + CACRT string `json:"caCrt"` + NamespaceID string `json:"namespaceId" norman:"type=reference[namespace]"` +} + +type DockerCredential struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Registries map[string]RegistryCredential `json:"registries"` +} + +type RegistryCredential struct { + Username string `json:"username"` + Password string `json:"password" norman:"writeOnly"` + Auth string `json:"auth"` +} + +type Certificate struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Certs string `json:"certs"` + Key string `json:"key" norman:"writeOnly"` + CertFingerprint string `json:"certFingerprint"` + CN string `json:"cn"` + Version string `json:"version"` + Issuer string `json:"issuer"` + IssuedAt string `json:"issuedAt"` + Algorithm string `json:"Algorithm"` + SerialNumber string `json:"serialNumber"` + KeySize string `json:"keySize"` + SubjectAlternativeNames string `json:"subjectAlternativeNames"` +} + +type BasicAuth struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Username string `json:"username"` + Password string `json:"password" norman:"writeOnly"` +} + +type SSHAuth struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + PrivateKey string `json:"privateKey"` +} diff --git a/config/context.go b/config/context.go index 97640657..4dd31fce 100644 --- a/config/context.go +++ b/config/context.go @@ -13,7 +13,9 @@ import ( managementv3 "github.com/rancher/types/apis/management.cattle.io/v3" managementSchema "github.com/rancher/types/apis/management.cattle.io/v3/schema" projectv3 "github.com/rancher/types/apis/project.cattle.io/v3" + projectSchema "github.com/rancher/types/apis/project.cattle.io/v3/schema" rbacv1 "github.com/rancher/types/apis/rbac.authorization.k8s.io/v1" + projectClient "github.com/rancher/types/client/project/v3" "github.com/sirupsen/logrus" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -24,6 +26,17 @@ import ( "k8s.io/client-go/tools/record" ) +var ( + ProjectTypes = []string{ + projectClient.BasicAuthType, + projectClient.CertificateType, + projectClient.DockerCredentialType, + projectClient.ServiceAccountTokenType, + projectClient.SecretType, + projectClient.SSHAuthType, + } +) + type ManagementContext struct { eventBroadcaster record.EventBroadcaster @@ -145,6 +158,11 @@ func NewManagementContext(config rest.Config) (*ManagementContext, error) { context.Schemas = types.NewSchemas(). AddSchemas(managementSchema.Schemas) + for _, projectType := range ProjectTypes { + schema := projectSchema.Schemas.Schema(&projectSchema.Version, projectType) + context.Schemas.AddSchema(*schema) + } + context.Scheme = runtime.NewScheme() managementv3.AddToScheme(context.Scheme)