| 141 | // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests |
| 142 | // Attempts to parse the given string into an OCIRef |
| 143 | export function getRef(output: Log, input: string): OCIRef | undefined { |
| 144 | // Normalize input by downcasing entire string |
| 145 | input = input.toLowerCase(); |
| 146 | |
| 147 | // Invalid if first character is a dot |
| 148 | if (input.startsWith('.')) { |
| 149 | output.write(`Input '${input}' failed validation. Expected input to not start with '.'`, LogLevel.Error); |
| 150 | return; |
| 151 | } |
| 152 | |
| 153 | const indexOfLastColon = input.lastIndexOf(':'); |
| 154 | const indexOfLastAtCharacter = input.lastIndexOf('@'); |
| 155 | |
| 156 | let resource = ''; |
| 157 | let tag: string | undefined = undefined; |
| 158 | let digest: string | undefined = undefined; |
| 159 | |
| 160 | // -- Resolve version |
| 161 | if (indexOfLastAtCharacter !== -1) { |
| 162 | // The version is specified by digest |
| 163 | // eg: ghcr.io/codspace/features/ruby@sha256:abcdefgh |
| 164 | resource = input.substring(0, indexOfLastAtCharacter); |
| 165 | const digestWithHashingAlgorithm = input.substring(indexOfLastAtCharacter + 1); |
| 166 | const splitOnColon = digestWithHashingAlgorithm.split(':'); |
| 167 | if (splitOnColon.length !== 2) { |
| 168 | output.write(`Failed to parse digest '${digestWithHashingAlgorithm}'. Expected format: 'sha256:abcdefghijk'`, LogLevel.Error); |
| 169 | return; |
| 170 | } |
| 171 | |
| 172 | if (splitOnColon[0] !== 'sha256') { |
| 173 | output.write(`Digest algorithm for input '${input}' failed validation. Expected hashing algorithm to be 'sha256'.`, LogLevel.Error); |
| 174 | return; |
| 175 | } |
| 176 | |
| 177 | if (!regexForVersionOrDigest.test(splitOnColon[1])) { |
| 178 | output.write(`Digest for input '${input}' failed validation. Expected digest to match regex '${regexForVersionOrDigest}'.`, LogLevel.Error); |
| 179 | } |
| 180 | |
| 181 | digest = digestWithHashingAlgorithm; |
| 182 | } else if (indexOfLastColon !== -1 && indexOfLastColon > input.lastIndexOf('/')) { |
| 183 | // The version is specified by tag |
| 184 | // eg: ghcr.io/codspace/features/ruby:1.0.0 |
| 185 | |
| 186 | // 1. The last colon is before the first slash (a port) |
| 187 | // eg: ghcr.io:8081/codspace/features/ruby |
| 188 | // 2. There is no tag at all |
| 189 | // eg: ghcr.io/codspace/features/ruby |
| 190 | resource = input.substring(0, indexOfLastColon); |
| 191 | tag = input.substring(indexOfLastColon + 1); |
| 192 | } else { |
| 193 | // There is no tag or digest, so assume 'latest' |
| 194 | resource = input; |
| 195 | tag = 'latest'; |
| 196 | } |
| 197 | |
| 198 | |
| 199 | if (tag && !regexForVersionOrDigest.test(tag)) { |
| 200 | output.write(`Tag '${tag}' for input '${input}' failed validation. Expected digest to match regex '${regexForVersionOrDigest}'.`, LogLevel.Error); |