github.com/BarDweller/libpak@v0.0.0-20230630201634-8dd5cfc15ec9/carton/buildmodule_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 "regexp" 24 25 "github.com/BarDweller/libpak/bard" 26 "github.com/BarDweller/libpak/internal" 27 "github.com/BurntSushi/toml" 28 ) 29 30 const ( 31 BuildModuleDependencyPattern = `(?m)([\s]*.*id[\s]+=[\s]+"%s"\n.*\n[\s]*version[\s]+=[\s]+")%s("\n[\s]*uri[\s]+=[\s]+").*("\n[\s]*sha256[\s]+=[\s]+").*(".*)` 32 BuildModuleDependencySubstitution = "${1}%s${2}%s${3}%s${4}" 33 ) 34 35 type BuildModuleDependency struct { 36 BuildModulePath string 37 ID string 38 SHA256 string 39 URI string 40 Version string 41 VersionPattern string 42 CPE string 43 CPEPattern string 44 PURL string 45 PURLPattern string 46 } 47 48 func (b BuildModuleDependency) Update(options ...Option) { 49 config := Config{ 50 exitHandler: internal.NewExitHandler(), 51 } 52 53 for _, option := range options { 54 config = option(config) 55 } 56 57 logger := bard.NewLogger(os.Stdout) 58 _, _ = fmt.Fprintf(logger.TitleWriter(), "\n%s\n", bard.FormatIdentity(b.ID, b.VersionPattern)) 59 logger.Headerf("Version: %s", b.Version) 60 logger.Headerf("PURL: %s", b.PURL) 61 logger.Headerf("CPEs: %s", b.CPE) 62 logger.Headerf("URI: %s", b.URI) 63 logger.Headerf("SHA256: %s", b.SHA256) 64 65 versionExp, err := regexp.Compile(b.VersionPattern) 66 if err != nil { 67 config.exitHandler.Error(fmt.Errorf("unable to compile version regex %s\n%w", b.VersionPattern, err)) 68 return 69 } 70 71 cpeExp, err := regexp.Compile(b.CPEPattern) 72 if err != nil { 73 config.exitHandler.Error(fmt.Errorf("unable to compile cpe regex %s\n%w", b.CPEPattern, err)) 74 return 75 } 76 77 purlExp, err := regexp.Compile(b.PURLPattern) 78 if err != nil { 79 config.exitHandler.Error(fmt.Errorf("unable to compile cpe regex %s\n%w", b.PURLPattern, err)) 80 return 81 } 82 83 c, err := os.ReadFile(b.BuildModulePath) 84 if err != nil { 85 config.exitHandler.Error(fmt.Errorf("unable to read %s\n%w", b.BuildModulePath, err)) 86 return 87 } 88 89 // save any leading comments, this is to preserve license headers 90 // inline comments will be lost 91 comments := []byte{} 92 for i, line := range bytes.SplitAfter(c, []byte("\n")) { 93 if bytes.HasPrefix(line, []byte("#")) || (i > 0 && len(bytes.TrimSpace(line)) == 0) { 94 comments = append(comments, line...) 95 } else { 96 break // stop on first comment 97 } 98 } 99 100 md := make(map[string]interface{}) 101 if err := toml.Unmarshal(c, &md); err != nil { 102 config.exitHandler.Error(fmt.Errorf("unable to decode md%s\n%w", b.BuildModulePath, err)) 103 return 104 } 105 106 metadataUnwrapped, found := md["metadata"] 107 if !found { 108 config.exitHandler.Error(fmt.Errorf("unable to find metadata block")) 109 return 110 } 111 112 metadata, ok := metadataUnwrapped.(map[string]interface{}) 113 if !ok { 114 config.exitHandler.Error(fmt.Errorf("unable to cast metadata")) 115 return 116 } 117 118 dependenciesUnwrapped, found := metadata["dependencies"] 119 if !found { 120 config.exitHandler.Error(fmt.Errorf("unable to find dependencies block")) 121 return 122 } 123 124 dependencies, ok := dependenciesUnwrapped.([]map[string]interface{}) 125 if !ok { 126 config.exitHandler.Error(fmt.Errorf("unable to cast dependencies")) 127 return 128 } 129 130 for _, dep := range dependencies { 131 depIdUnwrapped, found := dep["id"] 132 if !found { 133 continue 134 } 135 depId, ok := depIdUnwrapped.(string) 136 if !ok { 137 continue 138 } 139 140 if depId == b.ID { 141 depVersionUnwrapped, found := dep["version"] 142 if !found { 143 continue 144 } 145 146 depVersion, ok := depVersionUnwrapped.(string) 147 if !ok { 148 continue 149 } 150 if versionExp.MatchString(depVersion) { 151 dep["version"] = b.Version 152 dep["uri"] = b.URI 153 dep["sha256"] = b.SHA256 154 155 purlUnwrapped, found := dep["purl"] 156 if found { 157 purl, ok := purlUnwrapped.(string) 158 if ok { 159 dep["purl"] = purlExp.ReplaceAllString(purl, b.PURL) 160 } 161 } 162 163 cpesUnwrapped, found := dep["cpes"] 164 if found { 165 cpes, ok := cpesUnwrapped.([]interface{}) 166 if ok { 167 for i := 0; i < len(cpes); i++ { 168 cpe, ok := cpes[i].(string) 169 if !ok { 170 continue 171 } 172 173 cpes[i] = cpeExp.ReplaceAllString(cpe, b.CPE) 174 } 175 } 176 } 177 } 178 } 179 } 180 181 c, err = internal.Marshal(md) 182 if err != nil { 183 config.exitHandler.Error(fmt.Errorf("unable to encode md %s\n%w", b.BuildModulePath, err)) 184 return 185 } 186 187 c = append(comments, c...) 188 189 if err := os.WriteFile(b.BuildModulePath, c, 0644); err != nil { 190 config.exitHandler.Error(fmt.Errorf("unable to write %s\n%w", b.BuildModulePath, err)) 191 return 192 } 193 }