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  }