Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow hostname specific dependency mirrors #322

Merged
merged 8 commits into from
Apr 12, 2024
51 changes: 25 additions & 26 deletions dependency_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func NewDependencyCache(context libcnb.BuildContext) (DependencyCache, error) {
UserAgent: fmt.Sprintf("%s/%s", context.Buildpack.Info.ID, context.Buildpack.Info.Version),
Mappings: map[string]string{},
DependencyMirrors: map[string]string{},
Logger: bard.NewLogger(os.Stdout),
dmikusa marked this conversation as resolved.
Show resolved Hide resolved
dmikusa marked this conversation as resolved.
Show resolved Hide resolved
}
mappings, err := filterBindingsByType(context.Platform.Bindings, "dependency-mapping")
if err != nil {
Expand All @@ -101,11 +102,11 @@ func NewDependencyCache(context libcnb.BuildContext) (DependencyCache, error) {
}
cache.HttpClientTimeouts = *clientTimeouts

dependencyMirrors, err := getDependencyMirrors(context.Platform.Bindings)
bindingMirrors, err := filterBindingsByType(context.Platform.Bindings, "dependency-mirror")
if err != nil {
return DependencyCache{}, fmt.Errorf("unable to read dependency mirrors\n%w", err)
return DependencyCache{}, fmt.Errorf("unable to process dependency-mirror bindings\n%w", err)
}
cache.DependencyMirrors = dependencyMirrors
cache.setDependencyMirrors(bindingMirrors)

return cache, nil
}
Expand Down Expand Up @@ -150,39 +151,37 @@ func customizeHttpClientTimeouts() (*HttpClientTimeouts, error) {
}, nil
}

// Returns a key/value map with the URIs of all dependency mirrors. An empty map is returned if no mirrors are set.
// Mirror locations can be defined in bindings of type 'dependency-mirror' or using env variables prefixed with 'BP_DEPENDENCY_MIRROR'.
// Settings provided by env variables override those defined in bindings.
func getDependencyMirrors(bindings libcnb.Bindings) (map[string]string, error) {
dependencyMirrors, err := filterBindingsByType(bindings, "dependency-mirror")
if err != nil {
return nil, err
}
dependencyMirrorsFromEnv := getDependencyMirrorsFromEnv()
for host, uri := range dependencyMirrorsFromEnv {
dependencyMirrors[host] = uri
}
return dependencyMirrors, nil
}

// Returns a key/value map of all dependency mirrors set in environment variables.
func getDependencyMirrorsFromEnv() map[string]string {
mirrors := map[string]string{}
func (d *DependencyCache) setDependencyMirrors(bindingMirrors map[string]string) {
// Initialize with mirrors from bindings.
d.DependencyMirrors = bindingMirrors
// Add mirrors from env variables and override duplicate hostnames set in bindings.
envs := os.Environ()
for _, env := range envs {
envPair := strings.SplitN(env, "=", 2)
dmikusa marked this conversation as resolved.
Show resolved Hide resolved
if len(envPair) != 2 {
continue
}
hostnameSuffix, isMirror := strings.CutPrefix(envPair[0], "BP_DEPENDENCY_MIRROR")
hostnameEncoded, _ := strings.CutPrefix(hostnameSuffix, "_")
if isMirror {
mirrors[decodeHostnameEnv(hostnameEncoded)] = envPair[1]
hostnameEncoded, _ := strings.CutPrefix(hostnameSuffix, "_")
if strings.ToLower(hostnameEncoded) == "default" {
d.Logger.Bodyf("%s with illegal hostname 'default'. Please use BP_DEPENDENCY_MIRROR to set a default.",
color.YellowString("Ignored dependency mirror"))
continue
}
d.DependencyMirrors[decodeHostnameEnv(hostnameEncoded, d)] = envPair[1]
}
}
return mirrors
}

// Takes an encoded hostname (from env key) and returns the decoded version in lower case.
// Replaces double underscores (__) with one dash (-) and single underscores (_) with one period (.).
func decodeHostnameEnv(encodedHostname string) string {
func decodeHostnameEnv(encodedHostname string, d *DependencyCache) string {
if strings.ContainsAny(encodedHostname, "-.") || encodedHostname != strings.ToUpper(encodedHostname) {
d.Logger.Bodyf("%s These will be allowed but for best results across different shells, you should replace . characters with _ characters "+
"and - characters with __, and use all upper case letters. The buildpack will convert these back before using the mirror.",
color.YellowString("You have invalid characters in your mirror host environment variable."))
}
var decodedHostname string
if encodedHostname == "" {
dmikusa marked this conversation as resolved.
Show resolved Hide resolved
decodedHostname = "default"
Expand All @@ -199,7 +198,7 @@ func filterBindingsByType(bindings libcnb.Bindings, bindingType string) (map[str
for _, binding := range bindings {
if strings.ToLower(binding.Type) == bindingType {
for key, value := range binding.Secret {
if _, ok := filteredBindings[key]; ok {
if _, ok := filteredBindings[strings.ToLower(key)]; ok {
return nil, fmt.Errorf("multiple %s bindings found with duplicate keys %s", binding.Type, key)
}
filteredBindings[strings.ToLower(key)] = value
dmikusa marked this conversation as resolved.
Show resolved Hide resolved
Expand Down