Extract unpacks the feature from the image and returns the parsed specification.
(fs billy.Filesystem, devcontainerDir, directory, reference string)
| 93 | // Extract unpacks the feature from the image and returns the |
| 94 | // parsed specification. |
| 95 | func Extract(fs billy.Filesystem, devcontainerDir, directory, reference string) (*Spec, error) { |
| 96 | if strings.HasPrefix(reference, "./") { |
| 97 | if err := copy.Copy(filepath.Join(devcontainerDir, reference), directory, copy.Options{ |
| 98 | PreserveTimes: true, |
| 99 | PreserveOwner: true, |
| 100 | OnSymlink: func(src string) copy.SymlinkAction { |
| 101 | return copy.Shallow |
| 102 | }, |
| 103 | OnError: func(src, dest string, err error) error { |
| 104 | if err == nil { |
| 105 | return nil |
| 106 | } |
| 107 | return fmt.Errorf("copy error: %q -> %q: %w", reference, directory, err) |
| 108 | }, |
| 109 | }); err != nil { |
| 110 | return nil, err |
| 111 | } |
| 112 | } else if err := extractFromImage(fs, directory, reference); err != nil { |
| 113 | return nil, err |
| 114 | } |
| 115 | |
| 116 | installScriptPath := filepath.Join(directory, "install.sh") |
| 117 | _, err := fs.Stat(installScriptPath) |
| 118 | if err != nil { |
| 119 | if errors.Is(err, os.ErrNotExist) { |
| 120 | return nil, errors.New("install.sh must be in the root of the feature") |
| 121 | } |
| 122 | return nil, fmt.Errorf("stat install.sh: %w", err) |
| 123 | } |
| 124 | chmodder, ok := fs.(interface { |
| 125 | Chmod(name string, mode os.FileMode) error |
| 126 | }) |
| 127 | if ok { |
| 128 | // For some reason the filesystem abstraction doesn't support chmod. |
| 129 | // https://github.com/src-d/go-billy/issues/56 |
| 130 | err = chmodder.Chmod(installScriptPath, 0o755) |
| 131 | } |
| 132 | if err != nil { |
| 133 | return nil, fmt.Errorf("chmod install.sh: %w", err) |
| 134 | } |
| 135 | featureFile, err := fs.Open(filepath.Join(directory, "devcontainer-feature.json")) |
| 136 | if err != nil { |
| 137 | if errors.Is(err, os.ErrNotExist) { |
| 138 | return nil, errors.New("devcontainer-feature.json must be in the root of the feature") |
| 139 | } |
| 140 | return nil, fmt.Errorf("open devcontainer-feature.json: %w", err) |
| 141 | } |
| 142 | defer featureFile.Close() |
| 143 | featureFileBytes, err := io.ReadAll(featureFile) |
| 144 | if err != nil { |
| 145 | return nil, fmt.Errorf("read devcontainer-feature.json: %w", err) |
| 146 | } |
| 147 | standardizedFeatureFileBytes, err := hujson.Standardize(featureFileBytes) |
| 148 | if err != nil { |
| 149 | return nil, fmt.Errorf("standardize devcontainer-feature.json: %w", err) |
| 150 | } |
| 151 | var spec *Spec |
| 152 | if err := json.Unmarshal(standardizedFeatureFileBytes, &spec); err != nil { |