github.com/BarDweller/libpak@v0.0.0-20230630201634-8dd5cfc15ec9/carton/package_dependency.go (about) 1 /* 2 * Copyright 2018-2020 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package carton 18 19 import ( 20 "bytes" 21 "fmt" 22 "os" 23 "strings" 24 25 "github.com/BarDweller/libpak/bard" 26 "github.com/BarDweller/libpak/internal" 27 "github.com/BurntSushi/toml" 28 ) 29 30 type PackageDependency struct { 31 BuilderPath string 32 BuildpackPath string 33 ID string 34 Version string 35 PackagePath string 36 } 37 38 func (p PackageDependency) Update(options ...Option) { 39 config := Config{ 40 exitHandler: internal.NewExitHandler(), 41 } 42 43 for _, option := range options { 44 config = option(config) 45 } 46 47 logger := bard.NewLogger(os.Stdout) 48 _, _ = fmt.Fprintf(logger.TitleWriter(), "\n%s\n", bard.FormatIdentity(p.ID, p.Version)) 49 50 if p.BuilderPath != "" { 51 if err := updateFile(p.BuilderPath, updateByKey("buildpacks", p.ID, p.Version)); err != nil { 52 config.exitHandler.Error(fmt.Errorf("unable to update %s\n%w", p.BuilderPath, err)) 53 } 54 } 55 56 if p.PackagePath != "" { 57 if err := updateFile(p.PackagePath, updateByKey("dependencies", p.ID, p.Version)); err != nil { 58 config.exitHandler.Error(fmt.Errorf("unable to update %s\n%w", p.PackagePath, err)) 59 } 60 } 61 62 // Do we have a buildpack.toml with an order element? (composite buildpack) 63 if p.BuildpackPath != "" { 64 if err := updateFile(p.BuildpackPath, func(md map[string]interface{}) { 65 parts := strings.Split(p.ID, "/") 66 id := strings.Join(parts[len(parts)-2:], "/") 67 68 groupsUnwrapped, found := md["order"] 69 if !found { 70 return 71 } 72 73 groups, ok := groupsUnwrapped.([]map[string]interface{}) 74 if !ok { 75 return 76 } 77 78 for _, group := range groups { 79 buildpacksUnwrapped, found := group["group"] 80 if !found { 81 continue 82 } 83 84 buildpacks, ok := buildpacksUnwrapped.([]interface{}) 85 if !ok { 86 continue 87 } 88 89 for _, bpw := range buildpacks { 90 bp, ok := bpw.(map[string]interface{}) 91 if !ok { 92 continue 93 } 94 95 bpIdUnwrappd, found := bp["id"] 96 if !found { 97 continue 98 } 99 100 bpId, ok := bpIdUnwrappd.(string) 101 if !ok { 102 continue 103 } 104 105 if bpId == id { 106 bp["version"] = p.Version 107 } 108 } 109 } 110 }); err != nil { 111 config.exitHandler.Error(fmt.Errorf("unable to update %s\n%w", p.BuildpackPath, err)) 112 } 113 } 114 } 115 116 func updateByKey(key, id, version string) func(md map[string]interface{}) { 117 return func(md map[string]interface{}) { 118 valuesUnwrapped, found := md[key] 119 if !found { 120 return 121 } 122 123 values, ok := valuesUnwrapped.([]interface{}) 124 if !ok { 125 return 126 } 127 128 for _, bpw := range values { 129 bp, ok := bpw.(map[string]interface{}) 130 if !ok { 131 continue 132 } 133 134 uriUnwrapped, found := bp["uri"] 135 if !found { 136 continue 137 } 138 139 uri, ok := uriUnwrapped.(string) 140 if !ok { 141 continue 142 } 143 144 if strings.HasPrefix(uri, fmt.Sprintf("docker://%s", id)) { 145 parts := strings.Split(uri, ":") 146 bp["uri"] = fmt.Sprintf("%s:%s", strings.Join(parts[0:2], ":"), version) 147 } 148 } 149 } 150 } 151 152 func updateFile(cfgPath string, f func(md map[string]interface{})) error { 153 c, err := os.ReadFile(cfgPath) 154 if err != nil { 155 return fmt.Errorf("unable to read %s\n%w", cfgPath, err) 156 } 157 158 // save any leading comments, this is to preserve license headers 159 // inline comments will be lost 160 comments := []byte{} 161 for i, line := range bytes.SplitAfter(c, []byte("\n")) { 162 if bytes.HasPrefix(line, []byte("#")) || (i > 0 && len(bytes.TrimSpace(line)) == 0) { 163 comments = append(comments, line...) 164 } else { 165 break // stop on first comment 166 } 167 } 168 169 md := make(map[string]interface{}) 170 if err := toml.Unmarshal(c, &md); err != nil { 171 return fmt.Errorf("unable to decode md %s\n%w", cfgPath, err) 172 } 173 174 f(md) 175 176 b, err := internal.Marshal(md) 177 if err != nil { 178 return fmt.Errorf("unable to encode md %s\n%w", cfgPath, err) 179 } 180 181 b = append(comments, b...) 182 183 if err := os.WriteFile(cfgPath, b, 0644); err != nil { 184 return fmt.Errorf("unable to write %s\n%w", cfgPath, err) 185 } 186 187 return nil 188 }