// NewRockerfile reads parses Rockerfile from an io.Reader func NewRockerfile(name string, in io.Reader, vars template.Vars, funs template.Funs) (r *Rockerfile, err error) { r = &Rockerfile{ Name: name, Vars: vars, Funs: funs, } var ( source []byte content *bytes.Buffer ) if source, err = ioutil.ReadAll(in); err != nil { return nil, fmt.Errorf("Failed to read Rockerfile %s, error: %s", name, err) } r.Source = string(source) if content, err = template.Process(name, bytes.NewReader(source), vars, funs); err != nil { return nil, err } r.Content = content.String() // TODO: update parser from Docker if r.rootNode, err = parser.Parse(content); err != nil { return nil, err } return r, nil }
// ReadConfig reads and parses the config from io.Reader stream. // Before parsing it processes config through a template engine implemented in template.go. func ReadConfig(configName string, reader io.Reader, vars template.Vars, funcs map[string]interface{}, print bool) (*Config, error) { config := &Config{} basedir, err := os.Getwd() if err != nil { return nil, fmt.Errorf("Failed to get working dir, error: %s", err) } if configName == "-" { configName = "<STDIN>" } else { // if file given, process volume paths relative to the manifest file basedir = filepath.Dir(configName) } data, err := template.Process(configName, reader, vars, funcs) if err != nil { return nil, fmt.Errorf("Failed to process config template, error: %s", err) } if print { fmt.Print(data.String()) os.Exit(0) } if err := yaml.Unmarshal(data.Bytes(), config); err != nil { return nil, fmt.Errorf("Failed to parse YAML config, error: %s", err) } // empty namespace is a backward compatible docker-compose format // we will try to guess the namespace my parent directory name if config.Namespace == "" { parentDir := filepath.Base(basedir) config.Namespace = regexp.MustCompile("[^a-z0-9\\-\\_]").ReplaceAllString(parentDir, "") } // Save vars to config config.Vars = vars // Read extra data type ConfigExtra struct { Containers map[string]map[string]interface{} } extra := &ConfigExtra{} if err := yaml.Unmarshal(data.Bytes(), extra); err != nil { return nil, fmt.Errorf("Failed to parse YAML config extra properties, error: %s", err) } // Initialize YAML keys // Index yaml fields for better search yamlFields := make(map[string]bool) for _, v := range getYamlFields() { yamlFields[v] = true } // Function that gets HOME (initialize only once) homeMemo := "" getHome := func() (h string, err error) { if homeMemo == "" { if homeMemo, err = homedir.Dir(); err != nil { return "", err } } return homeMemo, nil } // Process aliases on the first run, have to do it before extends // because Golang randomizes maps, sometimes inherited containers // process earlier then dependencies; also do initial validation for name, container := range config.Containers { if container == nil { return nil, fmt.Errorf("Invalid specification for container `%s` in %s", name, configName) } // Handle aliases if container.Command != nil { if container.Cmd == nil { container.Cmd = container.Command } container.Command = nil } if container.Link != nil { if container.Links == nil { container.Links = container.Link } container.Link = nil } if container.Label != nil { if container.Labels == nil { container.Labels = container.Label } container.Label = nil } if container.Hosts != nil { if container.AddHost == nil { container.AddHost = container.Hosts } container.Hosts = nil } if container.ExtraHosts != nil { if container.AddHost == nil { container.AddHost = container.ExtraHosts } container.ExtraHosts = nil } if container.WorkingDir != nil { if container.Workdir == nil { container.Workdir = container.WorkingDir } container.WorkingDir = nil } if container.Environment != nil { if container.Env == nil { container.Env = container.Environment } container.Environment = nil } // Process extra data extraFields := map[string]interface{}{} for key, val := range extra.Containers[name] { if !yamlFields[key] { extraFields[key] = val } } if len(extraFields) > 0 { container.Extra = extraFields } // pretty.Println(name, container.Extra) } // Process extending containers configuration for name, container := range config.Containers { if container.Extends != "" { if container.Extends == name { return nil, fmt.Errorf("Container %s: cannot extend from itself", name) } if _, ok := config.Containers[container.Extends]; !ok { return nil, fmt.Errorf("Container %s: cannot find container %s to extend from", name, container.Extends) } // TODO: build dependency graph by extends hierarchy to allow multiple inheritance if config.Containers[container.Extends].Extends != "" { return nil, fmt.Errorf("Container %s: cannot extend from %s: multiple inheritance is not allowed yet", name, container.Extends) } container.ExtendFrom(config.Containers[container.Extends]) } // Validate image if container.Image == nil { return nil, fmt.Errorf("Image should be specified for container: %s", name) } img := imagename.NewFromString(*container.Image) if !img.IsStrict() && !img.HasVersionRange() && !img.All() { return nil, fmt.Errorf("Image `%s` for container `%s`: image without tag is not allowed", *container.Image, name) } // Set namespace for all containers inside for k := range container.VolumesFrom { container.VolumesFrom[k].DefaultNamespace(config.Namespace) } for k := range container.Links { container.Links[k].DefaultNamespace(config.Namespace) } for k := range container.WaitFor { container.WaitFor[k].DefaultNamespace(config.Namespace) } if container.Net != nil && container.Net.Type == "container" { container.Net.Container.DefaultNamespace(config.Namespace) } // Fix exposed ports for k, port := range container.Expose { if !strings.Contains(port, "/") { container.Expose[k] = port + "/tcp" } } // Process relative paths in volumes for i, volume := range container.Volumes { split := strings.SplitN(volume, ":", 2) if len(split) == 1 { continue } if strings.HasPrefix(split[0], "~") { home, err := getHome() if err != nil { return nil, fmt.Errorf("Failed to get HOME path, error: %s", err) } split[0] = strings.Replace(split[0], "~", home, 1) } if !path.IsAbs(split[0]) { split[0] = path.Join(basedir, split[0]) } container.Volumes[i] = strings.Join(split, ":") } } return config, nil }