/ components / execd / pkg / runtime / env.go
env.go
  1  // Copyright 2026 Alibaba Group Holding Ltd.
  2  //
  3  // Licensed under the Apache License, Version 2.0 (the "License");
  4  // you may not use this file except in compliance with the License.
  5  // You may obtain a copy of the License at
  6  //
  7  //     http://www.apache.org/licenses/LICENSE-2.0
  8  //
  9  // Unless required by applicable law or agreed to in writing, software
 10  // distributed under the License is distributed on an "AS IS" BASIS,
 11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  // See the License for the specific language governing permissions and
 13  // limitations under the License.
 14  
 15  package runtime
 16  
 17  import (
 18  	"fmt"
 19  	"os"
 20  	"strings"
 21  
 22  	"github.com/alibaba/opensandbox/execd/pkg/log"
 23  	"github.com/alibaba/opensandbox/execd/pkg/util/pathutil"
 24  )
 25  
 26  // loadExtraEnvFromFile reads key=value lines from EXECD_ENVS (if set).
 27  // Empty lines and lines starting with '#' are ignored.
 28  func loadExtraEnvFromFile() map[string]string {
 29  	path := os.Getenv("EXECD_ENVS")
 30  	if path == "" {
 31  		return nil
 32  	}
 33  	resolvedPath, err := pathutil.ExpandPath(path)
 34  	if err != nil {
 35  		log.Warn("EXECD_ENVS: failed to resolve file path %s: %v", path, err)
 36  		return nil
 37  	}
 38  
 39  	data, err := os.ReadFile(resolvedPath)
 40  	if err != nil {
 41  		log.Warn("EXECD_ENVS: failed to read file %s: %v", resolvedPath, err)
 42  		return nil
 43  	}
 44  
 45  	envs := make(map[string]string)
 46  	lines := strings.Split(string(data), "\n")
 47  	for _, line := range lines {
 48  		line = strings.TrimSpace(line)
 49  		if line == "" || strings.HasPrefix(line, "#") {
 50  			continue
 51  		}
 52  		kv := strings.SplitN(line, "=", 2)
 53  		if len(kv) != 2 {
 54  			log.Warn("EXECD_ENVS: skip malformed line: %s", line)
 55  			continue
 56  		}
 57  		envs[kv[0]] = os.ExpandEnv(kv[1])
 58  	}
 59  
 60  	return envs
 61  }
 62  
 63  // mergeEnvs overlays extra into base and returns a merged slice.
 64  func mergeEnvs(base []string, extra map[string]string) []string {
 65  	if len(extra) == 0 {
 66  		return base
 67  	}
 68  
 69  	merged := make(map[string]string, len(base)+len(extra))
 70  	for _, kv := range base {
 71  		pair := strings.SplitN(kv, "=", 2)
 72  		if len(pair) == 2 {
 73  			merged[pair[0]] = pair[1]
 74  		}
 75  	}
 76  
 77  	for k, v := range extra {
 78  		merged[k] = v
 79  	}
 80  
 81  	out := make([]string, 0, len(merged))
 82  	for k, v := range merged {
 83  		out = append(out, fmt.Sprintf("%s=%s", k, v))
 84  	}
 85  
 86  	return out
 87  }
 88  
 89  // mergeExtraEnvs merges environment maps from file and request-level overrides.
 90  func mergeExtraEnvs(fromFile, fromRequest map[string]string) map[string]string {
 91  	if len(fromRequest) == 0 {
 92  		return fromFile
 93  	}
 94  
 95  	merged := make(map[string]string, len(fromFile)+len(fromRequest))
 96  	for k, v := range fromFile {
 97  		merged[k] = v
 98  	}
 99  	for k, v := range fromRequest {
100  		merged[k] = v
101  	}
102  
103  	return merged
104  }