github.com/vshn/k8ify@v1.1.2-0.20240502214202-6c9ed3ef0bf4/pkg/ir/ir.go (about) 1 package ir 2 3 import ( 4 composeTypes "github.com/compose-spec/compose-go/types" 5 "github.com/vshn/k8ify/pkg/util" 6 "k8s.io/apimachinery/pkg/api/resource" 7 "strconv" 8 "strings" 9 ) 10 11 type Inputs struct { 12 Services map[string]*Service 13 Volumes map[string]*Volume 14 TargetCfg TargetCfg 15 } 16 17 func NewInputs() *Inputs { 18 return &Inputs{ 19 Services: make(map[string]*Service), 20 Volumes: make(map[string]*Volume), 21 } 22 } 23 24 func FromCompose(project *composeTypes.Project) *Inputs { 25 inputs := NewInputs() 26 27 // first find out all the regular ("parent") services 28 for _, composeService := range project.Services { 29 if util.PartOf(composeService.Labels) != nil { 30 continue 31 } 32 // `project.Services` is a list, so we use the name as reported by the 33 // service 34 inputs.Services[composeService.Name] = NewService(composeService.Name, composeService) 35 } 36 37 // then find all the parts that belong to a parent service and attach them to their parents 38 for _, composeService := range project.Services { 39 partOf := util.PartOf(composeService.Labels) 40 if partOf == nil { 41 continue 42 } 43 parent, ok := inputs.Services[*partOf] 44 if ok { 45 service := NewService(composeService.Name, composeService) 46 parent.AddPart(service) 47 } 48 } 49 50 for name, composeVolume := range project.Volumes { 51 // `project.CollectVolumes` is a map where the key is the volume name, while 52 // `volume.Name` is something else (the name prefixed with `_`???). So 53 // we use the key as the name. 54 inputs.Volumes[name] = NewVolume(name, composeVolume) 55 } 56 57 if targetCfg, ok := project.Extensions["x-targetCfg"]; ok { 58 if targetCfgMap, ok := targetCfg.(map[string]interface{}); ok { 59 inputs.TargetCfg = targetCfgMap 60 } 61 } 62 63 return inputs 64 } 65 66 // Service provides some k8ify-specific abstractions & utility around Compose 67 // service configurations. 68 type Service struct { 69 Name string 70 71 raw composeTypes.ServiceConfig 72 73 parts []*Service 74 } 75 76 func NewService(name string, composeService composeTypes.ServiceConfig) *Service { 77 return &Service{Name: name, raw: composeService, parts: make([]*Service, 0)} 78 } 79 80 // AsCompose returns the underlying compose config 81 // TODO(mh): make me obsolete! 82 func (s *Service) AsCompose() composeTypes.ServiceConfig { 83 return s.raw 84 } 85 86 func (s *Service) AddPart(part *Service) { 87 s.parts = append(s.parts, part) 88 } 89 90 func (s *Service) GetParts() []*Service { 91 return s.parts 92 } 93 94 // VolumeNames lists the names of all volumes that are mounted by this service 95 func (s *Service) VolumeNames() []string { 96 names := []string{} 97 98 for _, mount := range s.raw.Volumes { 99 if mount.Type != "volume" { 100 // We don't support anything else (yet) 101 continue 102 } 103 104 names = append(names, mount.Source) 105 } 106 107 return names 108 } 109 110 func (s *Service) Volumes(volumes map[string]*Volume) (map[string]*Volume, map[string]*Volume) { 111 rwoVolumes := make(map[string]*Volume) 112 rwxVolumes := make(map[string]*Volume) 113 for _, volumeName := range s.VolumeNames() { 114 volume := volumes[volumeName] 115 if volume.IsShared() { 116 rwxVolumes[volume.Name] = volume 117 } else { 118 rwoVolumes[volume.Name] = volume 119 } 120 } 121 return rwoVolumes, rwxVolumes 122 } 123 124 func (s *Service) IsSingleton() bool { 125 return util.IsSingleton(s.raw.Labels) 126 } 127 func (s *Service) Labels() map[string]string { 128 return s.raw.Labels 129 } 130 131 type PublishedPort struct { 132 ServicePort uint16 133 ContainerPort uint16 134 } 135 136 func (s *Service) GetPorts() []PublishedPort { 137 var publishedPorts []PublishedPort 138 for _, port := range s.raw.Ports { 139 publishedPort := PublishedPort{ 140 ServicePort: uint16(port.Target), // fall-back 141 ContainerPort: uint16(port.Target), 142 } 143 // port.Published can contain a range. Since we can't use this range for k8s we always use the start of the range instead. 144 portRange := strings.Split(port.Published, "-") 145 if len(portRange) > 0 { 146 p, err := strconv.ParseUint(portRange[0], 10, 16) 147 if err == nil { 148 publishedPort.ServicePort = uint16(p) 149 } 150 } 151 publishedPorts = append(publishedPorts, publishedPort) 152 } 153 return publishedPorts 154 } 155 156 // Volume provides some k8ify-specific abstractions & utility around Compose 157 // volume configurations. 158 type Volume struct { 159 Name string 160 161 raw composeTypes.VolumeConfig 162 } 163 164 func NewVolume(name string, composeVolume composeTypes.VolumeConfig) *Volume { 165 return &Volume{ 166 Name: name, 167 raw: composeVolume, 168 } 169 } 170 171 func (v *Volume) IsShared() bool { 172 return util.IsShared(v.raw.Labels) 173 } 174 func (v *Volume) IsSingleton() bool { 175 return util.IsSingleton(v.raw.Labels) 176 } 177 func (v *Volume) Labels() map[string]string { 178 return v.raw.Labels 179 } 180 181 func (v *Volume) Size(fallback string) resource.Quantity { 182 return util.StorageSize(v.raw.Labels, fallback) 183 } 184 func (v *Volume) SizeIsMissing() bool { 185 return util.StorageSizeRaw(v.raw.Labels) == nil 186 } 187 188 type TargetCfg map[string]interface{} 189 190 func (t TargetCfg) appsDomain() *string { 191 if value, ok := t["appsDomain"]; ok { 192 if domain, ok := value.(string); ok { 193 if strings.HasPrefix(domain, "*.") { 194 domain = domain[1:] 195 } 196 if !strings.HasPrefix(domain, ".") { 197 domain = "." + domain 198 } 199 if len(domain) < 2 { 200 return nil 201 } 202 return &domain 203 } 204 } 205 return nil 206 } 207 208 func (t TargetCfg) IsSubdomainOfAppsDomain(domain string) bool { 209 appsDomain := t.appsDomain() 210 if appsDomain == nil || domain == "" { 211 return false 212 } 213 domainComponents := strings.Split(domain, ".") 214 if len(domainComponents) < 2 { 215 return false 216 } 217 return domainComponents[0]+*appsDomain == domain 218 } 219 220 func (t TargetCfg) MaxExposeLength() int { 221 if value, ok := t["maxExposeLength"]; ok { 222 if length, ok := value.(int); ok { 223 return length 224 } 225 } 226 return 63 227 }