// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package hostnames

import (
	"fmt"
	"net"
	"os"
	"path/filepath"
	"strings"

	"github.com/hashicorp/nomad/plugins/drivers"
)

// GenerateEtcHostsMount writes a /etc/hosts file using the network spec's
// hosts configuration, and returns a mount config so that task drivers can
// bind-mount it into the resulting task's filesystem. The extraHosts
// parameter is expected to be the same format as the extra_hosts field from
// the Docker or containerd drivers: []string{"<hostname>:<ip address>"}
func GenerateEtcHostsMount(taskDir string, conf *drivers.NetworkIsolationSpec, extraHosts []string) (*drivers.MountConfig, error) {
	if conf == nil || conf.Mode != drivers.NetIsolationModeGroup {
		return nil, nil
	}
	hostsCfg := conf.HostsConfig
	if hostsCfg == nil || hostsCfg.Address == "" || hostsCfg.Hostname == "" {
		return nil, nil
	}

	var content strings.Builder
	fmt.Fprintf(&content, `# this file was generated by Nomad
127.0.0.1 localhost
::1 localhost
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

# this entry is the IP address and hostname of the allocation
# shared with tasks in the task group's network
%s %s
`, hostsCfg.Address, hostsCfg.Hostname)

	if len(extraHosts) > 0 {
		content.WriteString("\n# these entries are extra hosts added by the task config")
		for _, hostLine := range extraHosts {
			hostsEntry := strings.SplitN(hostLine, ":", 2)
			if len(hostsEntry) != 2 {
				return nil, fmt.Errorf("invalid hosts entry %q", hostLine)
			}
			if net.ParseIP(hostsEntry[1]) == nil {
				return nil, fmt.Errorf("invalid IP address %q", hostLine)
			}
			content.WriteString(fmt.Sprintf("\n%s %s", hostsEntry[1], hostsEntry[0]))
		}
		content.WriteString("\n")
	}

	path := filepath.Join(taskDir, "hosts")

	// tasks within an alloc should be able to share and modify the file, so
	// only write to it if it doesn't exist
	if _, err := os.Stat(path); os.IsNotExist(err) {
		err := os.WriteFile(path, []byte(content.String()), 0644)
		if err != nil {
			return nil, err
		}
	}

	// Note that we're not setting readonly. The file is in the task dir
	// anyways, so this lets the task overwrite its own hosts file if the
	// application knows better than Nomad here. Task drivers may override
	// this behavior.
	mount := &drivers.MountConfig{
		TaskPath:        "/etc/hosts",
		HostPath:        path,
		Readonly:        false,
		PropagationMode: "private",
	}

	return mount, nil
}
