2023-03-30 03:41:56 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
// package rules is used find and modify Bazel rules in WORKSPACE and bzl files.
|
|
|
|
package rules
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/bazelbuild/buildtools/build"
|
|
|
|
"golang.org/x/exp/slices"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Rules is used to find and modify Bazel rules of a set of rule kinds in WORKSPACE and .bzl files.
|
|
|
|
// Filter is a list of rule kinds to consider.
|
|
|
|
// If filter is empty, all rules are considered.
|
|
|
|
func Rules(file *build.File, filter []string) (rules []*build.Rule) {
|
|
|
|
allRules := file.Rules("")
|
|
|
|
if len(filter) == 0 {
|
|
|
|
return allRules
|
|
|
|
}
|
|
|
|
ruleLoop:
|
|
|
|
for _, rule := range allRules {
|
|
|
|
for _, ruleKind := range filter {
|
|
|
|
if rule.Kind() == ruleKind {
|
|
|
|
rules = append(rules, rule)
|
|
|
|
continue ruleLoop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-05 11:32:51 -04:00
|
|
|
return rules
|
2023-03-30 03:41:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ValidatePinned checks if the given rule is a pinned dependency rule.
|
|
|
|
// That is, if it has a name, either a url or urls attribute, and a sha256 attribute.
|
|
|
|
func ValidatePinned(rule *build.Rule) (validationErrs []error) {
|
|
|
|
if rule.Name() == "" {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule has no name"))
|
|
|
|
}
|
|
|
|
|
|
|
|
hasURL := rule.Attr("url") != nil
|
|
|
|
hasURLs := rule.Attr("urls") != nil
|
|
|
|
if !hasURL && !hasURLs {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule has no url or urls attribute"))
|
|
|
|
}
|
|
|
|
if hasURL && hasURLs {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule has both url and urls attribute"))
|
|
|
|
}
|
|
|
|
if hasURL {
|
|
|
|
url := rule.AttrString("url")
|
|
|
|
if url == "" {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule has empty url attribute"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if hasURLs {
|
|
|
|
urls := rule.AttrStrings("urls")
|
|
|
|
if len(urls) == 0 {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule has empty urls list attribute"))
|
|
|
|
} else {
|
|
|
|
for _, url := range urls {
|
|
|
|
if url == "" {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule has empty url in urls attribute"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if rule.Attr("sha256") == nil {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule has no sha256 attribute"))
|
|
|
|
} else {
|
|
|
|
sha256 := rule.AttrString("sha256")
|
|
|
|
if sha256 == "" {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule has empty sha256 attribute"))
|
|
|
|
}
|
|
|
|
}
|
2023-04-05 11:32:51 -04:00
|
|
|
return validationErrs
|
2023-03-30 03:41:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check checks if a dependency rule is normalized and contains a mirror url.
|
|
|
|
// All errors reported by this function can be fixed by calling AddURLs and Normalize.
|
|
|
|
func Check(rule *build.Rule) (validationErrs []error) {
|
|
|
|
hasURL := rule.Attr("url") != nil
|
|
|
|
if hasURL {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule has url (singular) attribute"))
|
|
|
|
}
|
|
|
|
urls := rule.AttrStrings("urls")
|
|
|
|
sorted := make([]string, len(urls))
|
|
|
|
copy(sorted, urls)
|
|
|
|
sortURLs(sorted)
|
|
|
|
for i, url := range urls {
|
|
|
|
if url != sorted[i] {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule has unsorted urls attributes"))
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !HasMirrorURL(rule) {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rule is not mirrored"))
|
|
|
|
}
|
|
|
|
if rule.Kind() == "http_archive" && rule.Attr("type") == nil {
|
|
|
|
validationErrs = append(validationErrs, errors.New("http_archive rule has no type attribute"))
|
|
|
|
}
|
|
|
|
if rule.Kind() == "rpm" && len(urls) != 1 {
|
|
|
|
validationErrs = append(validationErrs, errors.New("rpm rule has unstable urls that are not the edgeless mirror"))
|
|
|
|
}
|
2023-04-05 11:32:51 -04:00
|
|
|
return validationErrs
|
2023-03-30 03:41:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Normalize normalizes a rule and returns true if the rule was changed.
|
|
|
|
func Normalize(rule *build.Rule) (changed bool) {
|
|
|
|
changed = addTypeAttribute(rule)
|
|
|
|
urls := GetURLs(rule)
|
|
|
|
normalizedURLS := append([]string{}, urls...)
|
|
|
|
// rpm rules must have exactly one url (the edgeless mirror)
|
|
|
|
if mirrorU, err := mirrorURL(rule); rule.Kind() == "rpm" && err == nil {
|
|
|
|
normalizedURLS = []string{mirrorU}
|
|
|
|
}
|
|
|
|
sortURLs(normalizedURLS)
|
|
|
|
normalizedURLS = deduplicateURLs(normalizedURLS)
|
|
|
|
if slices.Equal(urls, normalizedURLS) && rule.Attr("url") == nil {
|
2023-04-05 11:32:51 -04:00
|
|
|
return changed
|
2023-03-30 03:41:56 -04:00
|
|
|
}
|
|
|
|
setURLs(rule, normalizedURLS)
|
2023-04-05 11:32:51 -04:00
|
|
|
return true
|
2023-03-30 03:41:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddURLs adds a url to a rule.
|
|
|
|
func AddURLs(rule *build.Rule, urls []string) {
|
|
|
|
existingURLs := GetURLs(rule)
|
|
|
|
existingURLs = append(existingURLs, urls...)
|
|
|
|
sortURLs(existingURLs)
|
|
|
|
deduplicatedURLs := deduplicateURLs(existingURLs)
|
|
|
|
setURLs(rule, deduplicatedURLs)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetHash returns the sha256 hash of a rule.
|
|
|
|
func GetHash(rule *build.Rule) (string, error) {
|
|
|
|
hash := rule.AttrString("sha256")
|
|
|
|
if hash == "" {
|
|
|
|
return "", fmt.Errorf("rule %s has empty or missing sha256 attribute", rule.Name())
|
|
|
|
}
|
|
|
|
return hash, nil
|
|
|
|
}
|
|
|
|
|
2023-04-05 11:32:51 -04:00
|
|
|
// SetHash sets the sha256 hash of a rule.
|
|
|
|
func SetHash(rule *build.Rule, hash string) {
|
|
|
|
rule.SetAttr("sha256", &build.StringExpr{Value: hash})
|
|
|
|
}
|
|
|
|
|
2023-03-30 03:41:56 -04:00
|
|
|
// GetURLs returns the sorted urls of a rule.
|
|
|
|
func GetURLs(rule *build.Rule) []string {
|
|
|
|
urls := rule.AttrStrings("urls")
|
|
|
|
url := rule.AttrString("url")
|
|
|
|
if url != "" {
|
|
|
|
urls = append(urls, url)
|
|
|
|
}
|
|
|
|
return urls
|
|
|
|
}
|
|
|
|
|
2023-04-05 11:32:51 -04:00
|
|
|
// HasMirrorURL returns true if the rule has a url from the Edgeless mirror
|
|
|
|
// with the correct hash.
|
2023-03-30 03:41:56 -04:00
|
|
|
func HasMirrorURL(rule *build.Rule) bool {
|
|
|
|
_, err := mirrorURL(rule)
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
2023-04-05 11:32:51 -04:00
|
|
|
// PrepareUpgrade prepares a rule for an upgrade
|
|
|
|
// by removing all urls that are not upstream urls.
|
|
|
|
// and removing the hash attribute.
|
|
|
|
// it returns true if the rule was changed.
|
|
|
|
func PrepareUpgrade(rule *build.Rule) (changed bool, err error) {
|
|
|
|
upstreamURLs, err := UpstreamURLs(rule)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
setURLs(rule, upstreamURLs)
|
|
|
|
rule.DelAttr("sha256")
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpstreamURLs returns the upstream urls (non-mirror urls) of a rule.
|
|
|
|
func UpstreamURLs(rule *build.Rule) (urls []string, err error) {
|
|
|
|
urls = GetURLs(rule)
|
|
|
|
var upstreamURLs []string
|
|
|
|
for _, url := range urls {
|
|
|
|
if isUpstreamURL(url) {
|
|
|
|
upstreamURLs = append(upstreamURLs, url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(upstreamURLs) == 0 {
|
|
|
|
return nil, ErrNoUpstreamURL
|
|
|
|
}
|
|
|
|
return upstreamURLs, nil
|
|
|
|
}
|
|
|
|
|
2023-03-30 03:41:56 -04:00
|
|
|
func deduplicateURLs(urls []string) (deduplicated []string) {
|
|
|
|
seen := make(map[string]bool)
|
|
|
|
for _, url := range urls {
|
|
|
|
if !seen[url] {
|
|
|
|
deduplicated = append(deduplicated, url)
|
|
|
|
seen[url] = true
|
|
|
|
}
|
|
|
|
}
|
2023-04-05 11:32:51 -04:00
|
|
|
return deduplicated
|
2023-03-30 03:41:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// addTypeAttribute adds the type attribute to http_archive rules if it is missing.
|
|
|
|
// it returns true if the rule was changed.
|
|
|
|
// it returns an error if the rule does not have enough information to add the type attribute.
|
|
|
|
func addTypeAttribute(rule *build.Rule) bool {
|
|
|
|
// only http_archive rules have a type attribute
|
|
|
|
if rule.Kind() != "http_archive" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if rule.Attr("type") != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// iterate over all URLs and check if they have a known archive type
|
|
|
|
var archiveType string
|
|
|
|
urlLoop:
|
|
|
|
for _, url := range GetURLs(rule) {
|
|
|
|
switch {
|
|
|
|
case strings.HasSuffix(url, ".aar"):
|
|
|
|
archiveType = "aar"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".ar"):
|
|
|
|
archiveType = "ar"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".deb"):
|
|
|
|
archiveType = "deb"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".jar"):
|
|
|
|
archiveType = "jar"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".tar.bz2"):
|
|
|
|
archiveType = "tar.bz2"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".tar.gz"):
|
|
|
|
archiveType = "tar.gz"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".tar.xz"):
|
|
|
|
archiveType = "tar.xz"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".tar.zst"):
|
|
|
|
archiveType = "tar.zst"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".tar"):
|
|
|
|
archiveType = "tar"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".tgz"):
|
|
|
|
archiveType = "tgz"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".txz"):
|
|
|
|
archiveType = "txz"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".tzst"):
|
|
|
|
archiveType = "tzst"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".war"):
|
|
|
|
archiveType = "war"
|
|
|
|
break urlLoop
|
|
|
|
case strings.HasSuffix(url, ".zip"):
|
|
|
|
archiveType = "zip"
|
|
|
|
break urlLoop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if archiveType == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
rule.SetAttr("type", &build.StringExpr{Value: archiveType})
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// mirrorURL returns the first mirror URL for a rule.
|
|
|
|
func mirrorURL(rule *build.Rule) (string, error) {
|
2023-04-05 11:32:51 -04:00
|
|
|
hash, err := GetHash(rule)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2023-03-30 03:41:56 -04:00
|
|
|
urls := GetURLs(rule)
|
|
|
|
for _, url := range urls {
|
2023-04-05 11:32:51 -04:00
|
|
|
if strings.HasPrefix(url, edgelessMirrorPrefix) && strings.HasSuffix(url, hash) {
|
2023-03-30 03:41:56 -04:00
|
|
|
return url, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", fmt.Errorf("rule %s has no mirror url", rule.Name())
|
|
|
|
}
|
|
|
|
|
|
|
|
func setURLs(rule *build.Rule, urls []string) {
|
|
|
|
// delete single url attribute if it exists
|
|
|
|
rule.DelAttr("url")
|
|
|
|
urlsAttr := []build.Expr{}
|
|
|
|
for _, url := range urls {
|
|
|
|
urlsAttr = append(urlsAttr, &build.StringExpr{Value: url})
|
|
|
|
}
|
|
|
|
rule.SetAttr("urls", &build.ListExpr{List: urlsAttr, ForceMultiLine: true})
|
|
|
|
}
|
|
|
|
|
|
|
|
func sortURLs(urls []string) {
|
|
|
|
// Bazel mirror should be first
|
|
|
|
// edgeless mirror should be second
|
|
|
|
// other urls should be last
|
|
|
|
// if there are multiple urls from the same mirror, they should be sorted alphabetically
|
|
|
|
sort.Slice(urls, func(i, j int) bool {
|
|
|
|
rank := func(url string) int {
|
|
|
|
if strings.HasPrefix(url, bazelMirrorPrefix) {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(url, edgelessMirrorPrefix) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 2
|
|
|
|
}
|
|
|
|
if rank(urls[i]) != rank(urls[j]) {
|
|
|
|
return rank(urls[i]) < rank(urls[j])
|
|
|
|
}
|
|
|
|
return urls[i] < urls[j]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-04-05 11:32:51 -04:00
|
|
|
func isUpstreamURL(url string) bool {
|
|
|
|
return !strings.HasPrefix(url, bazelMirrorPrefix) && !strings.HasPrefix(url, edgelessMirrorPrefix)
|
2023-03-30 03:41:56 -04:00
|
|
|
}
|
|
|
|
|
2023-04-05 11:32:51 -04:00
|
|
|
var (
|
|
|
|
// SupportedRules is a list of all rules that can be mirrored.
|
|
|
|
SupportedRules = []string{
|
|
|
|
"http_archive",
|
|
|
|
"http_file",
|
|
|
|
"rpm",
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrNoUpstreamURL is returned when a rule has no upstream URL.
|
|
|
|
ErrNoUpstreamURL = errors.New("rule has no upstream URL")
|
|
|
|
)
|
|
|
|
|
2023-03-30 03:41:56 -04:00
|
|
|
const (
|
|
|
|
bazelMirrorPrefix = "https://mirror.bazel.build/"
|
|
|
|
edgelessMirrorPrefix = "https://cdn.confidential.cloud/constellation/cas/sha256/"
|
|
|
|
)
|