| 180 | } |
| 181 | |
| 182 | func analyzeDockerfile(path string) (*report, error) { |
| 183 | fullPath, err := filepath.Abs(path) |
| 184 | if err != nil { |
| 185 | return nil, err |
| 186 | } |
| 187 | |
| 188 | rawInstructions, err := readInstructions(fullPath) |
| 189 | if err != nil { |
| 190 | return nil, err |
| 191 | } |
| 192 | if len(rawInstructions) == 0 { |
| 193 | return nil, fmt.Errorf("no Dockerfile instructions found in %s", fullPath) |
| 194 | } |
| 195 | |
| 196 | var instructions []parsedInstruction |
| 197 | for _, raw := range rawInstructions { |
| 198 | parsed, err := parseInstruction(raw) |
| 199 | if err != nil { |
| 200 | return nil, err |
| 201 | } |
| 202 | instructions = append(instructions, parsed) |
| 203 | } |
| 204 | |
| 205 | rep := &report{ |
| 206 | FilePath: fullPath, |
| 207 | } |
| 208 | |
| 209 | var stageIndex = -1 |
| 210 | stageAliases := map[string]int{} |
| 211 | |
| 212 | for _, inst := range instructions { |
| 213 | if inst.Keyword == "" { |
| 214 | continue |
| 215 | } |
| 216 | |
| 217 | if inst.Keyword == "FROM" { |
| 218 | stageIndex++ |
| 219 | stage := ensureStage(rep, stageIndex) |
| 220 | base, alias := parseFrom(inst.Args) |
| 221 | if base == "" { |
| 222 | return nil, fmt.Errorf("line %d: FROM instruction missing base image", inst.Line) |
| 223 | } |
| 224 | stage.Stage.Base = base |
| 225 | stage.Stage.Name = alias |
| 226 | if alias != "" { |
| 227 | stageAliases[strings.ToLower(alias)] = stageIndex |
| 228 | } |
| 229 | stageAliases[fmt.Sprintf("%d", stageIndex)] = stageIndex |
| 230 | layer := buildLayer(inst, descriptorFor(inst.Keyword), []string{ |
| 231 | fmt.Sprintf("Stage resets here and pulls %q.", base), |
| 232 | }) |
| 233 | if alias != "" { |
| 234 | layer.Notes = append(layer.Notes, fmt.Sprintf("Alias %q lets you reference this stage via COPY --from=%s.", alias, alias)) |
| 235 | } |
| 236 | layer.Number = len(stage.Layers) + 1 |
| 237 | stage.Layers = append(stage.Layers, layer) |
| 238 | continue |
| 239 | } |