github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgenutil/util.go (about) 1 package specgenutil 2 3 import ( 4 "io/ioutil" 5 "net" 6 "os" 7 "strconv" 8 "strings" 9 10 "github.com/containers/common/libnetwork/types" 11 "github.com/containers/common/pkg/config" 12 storageTypes "github.com/containers/storage/types" 13 "github.com/pkg/errors" 14 "github.com/sirupsen/logrus" 15 ) 16 17 // ReadPodIDFile reads the specified file and returns its content (i.e., first 18 // line). 19 func ReadPodIDFile(path string) (string, error) { 20 content, err := ioutil.ReadFile(path) 21 if err != nil { 22 return "", errors.Wrap(err, "error reading pod ID file") 23 } 24 return strings.Split(string(content), "\n")[0], nil 25 } 26 27 // ReadPodIDFiles reads the specified files and returns their content (i.e., 28 // first line). 29 func ReadPodIDFiles(files []string) ([]string, error) { 30 ids := []string{} 31 for _, file := range files { 32 id, err := ReadPodIDFile(file) 33 if err != nil { 34 return nil, err 35 } 36 ids = append(ids, id) 37 } 38 return ids, nil 39 } 40 41 // CreateExpose parses user-provided exposed port definitions and converts them 42 // into SpecGen format. 43 // TODO: The SpecGen format should really handle ranges more sanely - we could 44 // be massively inflating what is sent over the wire with a large range. 45 func CreateExpose(expose []string) (map[uint16]string, error) { 46 toReturn := make(map[uint16]string) 47 48 for _, e := range expose { 49 // Check for protocol 50 proto := "tcp" 51 splitProto := strings.Split(e, "/") 52 if len(splitProto) > 2 { 53 return nil, errors.Errorf("invalid expose format - protocol can only be specified once") 54 } else if len(splitProto) == 2 { 55 proto = splitProto[1] 56 } 57 58 // Check for a range 59 start, len, err := parseAndValidateRange(splitProto[0]) 60 if err != nil { 61 return nil, err 62 } 63 64 var index uint16 65 for index = 0; index < len; index++ { 66 portNum := start + index 67 protocols, ok := toReturn[portNum] 68 if !ok { 69 toReturn[portNum] = proto 70 } else { 71 newProto := strings.Join(append(strings.Split(protocols, ","), strings.Split(proto, ",")...), ",") 72 toReturn[portNum] = newProto 73 } 74 } 75 } 76 77 return toReturn, nil 78 } 79 80 // CreatePortBindings iterates ports mappings into SpecGen format. 81 func CreatePortBindings(ports []string) ([]types.PortMapping, error) { 82 // --publish is formatted as follows: 83 // [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol] 84 toReturn := make([]types.PortMapping, 0, len(ports)) 85 86 for _, p := range ports { 87 var ( 88 ctrPort string 89 proto, hostIP, hostPort *string 90 ) 91 92 splitProto := strings.Split(p, "/") 93 switch len(splitProto) { 94 case 1: 95 // No protocol was provided 96 case 2: 97 proto = &(splitProto[1]) 98 default: 99 return nil, errors.Errorf("invalid port format - protocol can only be specified once") 100 } 101 102 remainder := splitProto[0] 103 haveV6 := false 104 105 // Check for an IPv6 address in brackets 106 splitV6 := strings.Split(remainder, "]") 107 switch len(splitV6) { 108 case 1: 109 // Do nothing, proceed as before 110 case 2: 111 // We potentially have an IPv6 address 112 haveV6 = true 113 if !strings.HasPrefix(splitV6[0], "[") { 114 return nil, errors.Errorf("invalid port format - IPv6 addresses must be enclosed by []") 115 } 116 if !strings.HasPrefix(splitV6[1], ":") { 117 return nil, errors.Errorf("invalid port format - IPv6 address must be followed by a colon (':')") 118 } 119 ipNoPrefix := strings.TrimPrefix(splitV6[0], "[") 120 hostIP = &ipNoPrefix 121 remainder = strings.TrimPrefix(splitV6[1], ":") 122 default: 123 return nil, errors.Errorf("invalid port format - at most one IPv6 address can be specified in a --publish") 124 } 125 126 splitPort := strings.Split(remainder, ":") 127 switch len(splitPort) { 128 case 1: 129 if haveV6 { 130 return nil, errors.Errorf("invalid port format - must provide host and destination port if specifying an IP") 131 } 132 ctrPort = splitPort[0] 133 case 2: 134 hostPort = &(splitPort[0]) 135 ctrPort = splitPort[1] 136 case 3: 137 if haveV6 { 138 return nil, errors.Errorf("invalid port format - when v6 address specified, must be [ipv6]:hostPort:ctrPort") 139 } 140 hostIP = &(splitPort[0]) 141 hostPort = &(splitPort[1]) 142 ctrPort = splitPort[2] 143 default: 144 return nil, errors.Errorf("invalid port format - format is [[hostIP:]hostPort:]containerPort") 145 } 146 147 newPort, err := parseSplitPort(hostIP, hostPort, ctrPort, proto) 148 if err != nil { 149 return nil, err 150 } 151 152 toReturn = append(toReturn, newPort) 153 } 154 155 return toReturn, nil 156 } 157 158 // parseSplitPort parses individual components of the --publish flag to produce 159 // a single port mapping in SpecGen format. 160 func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (types.PortMapping, error) { 161 newPort := types.PortMapping{} 162 if ctrPort == "" { 163 return newPort, errors.Errorf("must provide a non-empty container port to publish") 164 } 165 ctrStart, ctrLen, err := parseAndValidateRange(ctrPort) 166 if err != nil { 167 return newPort, errors.Wrapf(err, "error parsing container port") 168 } 169 newPort.ContainerPort = ctrStart 170 newPort.Range = ctrLen 171 172 if protocol != nil { 173 if *protocol == "" { 174 return newPort, errors.Errorf("must provide a non-empty protocol to publish") 175 } 176 newPort.Protocol = *protocol 177 } 178 if hostIP != nil { 179 if *hostIP == "" { 180 return newPort, errors.Errorf("must provide a non-empty container host IP to publish") 181 } else if *hostIP != "0.0.0.0" { 182 // If hostIP is 0.0.0.0, leave it unset - CNI treats 183 // 0.0.0.0 and empty differently, Docker does not. 184 testIP := net.ParseIP(*hostIP) 185 if testIP == nil { 186 return newPort, errors.Errorf("cannot parse %q as an IP address", *hostIP) 187 } 188 newPort.HostIP = testIP.String() 189 } 190 } 191 if hostPort != nil { 192 if *hostPort == "" { 193 // Set 0 as a placeholder. The server side of Specgen 194 // will find a random, open, unused port to use. 195 newPort.HostPort = 0 196 } else { 197 hostStart, hostLen, err := parseAndValidateRange(*hostPort) 198 if err != nil { 199 return newPort, errors.Wrapf(err, "error parsing host port") 200 } 201 if hostLen != ctrLen { 202 return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen) 203 } 204 newPort.HostPort = hostStart 205 } 206 } 207 208 hport := newPort.HostPort 209 logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol) 210 211 return newPort, nil 212 } 213 214 // Parse and validate a port range. 215 // Returns start port, length of range, error. 216 func parseAndValidateRange(portRange string) (uint16, uint16, error) { 217 splitRange := strings.Split(portRange, "-") 218 if len(splitRange) > 2 { 219 return 0, 0, errors.Errorf("invalid port format - port ranges are formatted as startPort-stopPort") 220 } 221 222 if splitRange[0] == "" { 223 return 0, 0, errors.Errorf("port numbers cannot be negative") 224 } 225 226 startPort, err := parseAndValidatePort(splitRange[0]) 227 if err != nil { 228 return 0, 0, err 229 } 230 231 var rangeLen uint16 = 1 232 if len(splitRange) == 2 { 233 if splitRange[1] == "" { 234 return 0, 0, errors.Errorf("must provide ending number for port range") 235 } 236 endPort, err := parseAndValidatePort(splitRange[1]) 237 if err != nil { 238 return 0, 0, err 239 } 240 if endPort <= startPort { 241 return 0, 0, errors.Errorf("the end port of a range must be higher than the start port - %d is not higher than %d", endPort, startPort) 242 } 243 // Our range is the total number of ports 244 // involved, so we need to add 1 (8080:8081 is 245 // 2 ports, for example, not 1) 246 rangeLen = endPort - startPort + 1 247 } 248 249 return startPort, rangeLen, nil 250 } 251 252 // Turn a single string into a valid U16 port. 253 func parseAndValidatePort(port string) (uint16, error) { 254 num, err := strconv.Atoi(port) 255 if err != nil { 256 return 0, errors.Wrapf(err, "invalid port number") 257 } 258 if num < 1 || num > 65535 { 259 return 0, errors.Errorf("port numbers must be between 1 and 65535 (inclusive), got %d", num) 260 } 261 return uint16(num), nil 262 } 263 264 func CreateExitCommandArgs(storageConfig storageTypes.StoreOptions, config *config.Config, syslog, rm, exec bool) ([]string, error) { 265 // We need a cleanup process for containers in the current model. 266 // But we can't assume that the caller is Podman - it could be another 267 // user of the API. 268 // As such, provide a way to specify a path to Podman, so we can 269 // still invoke a cleanup process. 270 271 podmanPath, err := os.Executable() 272 if err != nil { 273 return nil, err 274 } 275 276 command := []string{podmanPath, 277 "--root", storageConfig.GraphRoot, 278 "--runroot", storageConfig.RunRoot, 279 "--log-level", logrus.GetLevel().String(), 280 "--cgroup-manager", config.Engine.CgroupManager, 281 "--tmpdir", config.Engine.TmpDir, 282 "--network-config-dir", config.Network.NetworkConfigDir, 283 "--network-backend", config.Network.NetworkBackend, 284 "--volumepath", config.Engine.VolumePath, 285 } 286 if config.Engine.OCIRuntime != "" { 287 command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...) 288 } 289 if storageConfig.GraphDriverName != "" { 290 command = append(command, []string{"--storage-driver", storageConfig.GraphDriverName}...) 291 } 292 for _, opt := range storageConfig.GraphDriverOptions { 293 command = append(command, []string{"--storage-opt", opt}...) 294 } 295 if config.Engine.EventsLogger != "" { 296 command = append(command, []string{"--events-backend", config.Engine.EventsLogger}...) 297 } 298 299 if syslog { 300 command = append(command, "--syslog") 301 } 302 command = append(command, []string{"container", "cleanup"}...) 303 304 if rm { 305 command = append(command, "--rm") 306 } 307 308 // This has to be absolutely last, to ensure that the exec session ID 309 // will be added after it by Libpod. 310 if exec { 311 command = append(command, "--exec") 312 } 313 314 return command, nil 315 }