mirror of https://github.com/k3s-io/k3s
commit
afde45abfa
|
@ -0,0 +1,79 @@
|
|||
# Agnhost
|
||||
|
||||
## Overview
|
||||
|
||||
There are significant differences between Linux and Windows, especially in the way
|
||||
something can be obtained or tested. For example, the DNS suffix list can be found in
|
||||
`/etc/resolv.conf` on Linux, but on Windows, such file does not exist, the same
|
||||
information could retrieved through other means. To combat those differences,
|
||||
`agnhost` was created.
|
||||
|
||||
`agnhost` is an extendable CLI that behaves and outputs the same expected content,
|
||||
no matter the underlying OS. The name itself reflects this idea, being a portmanteau
|
||||
word of the words agnost and host.
|
||||
|
||||
The image was created for testing purposes, reducing the need for having different test
|
||||
cases for the same tested behaviour.
|
||||
|
||||
## Usage
|
||||
|
||||
The `agnhost` binary is a CLI with the following subcommands:
|
||||
|
||||
- `dns-suffix`: It will output the host's configured DNS suffix list, separated by commas.
|
||||
- `dns-server-list`: It will output the host's configured DNS servers, separated by commas.
|
||||
- `etc-hosts`: It will output the contents of host's `hosts` file. This file's location
|
||||
is `/etc/hosts` on Linux, while on Windows it is `C:/Windows/System32/drivers/etc/hosts`.
|
||||
- `pause`: It will pause the execution of the binary. This can be used for containers
|
||||
which have to be kept in a `Running` state for various purposes, including executing
|
||||
other `agnhost` commands.
|
||||
- `help`: Prints the binary's help menu. Additionally, it can be followed by another
|
||||
subcommand in order to get more information about that subcommand, including its
|
||||
possible arguments.
|
||||
|
||||
For example, let's consider the following `pod.yaml` file:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-agnhost
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- dns-suffix
|
||||
image: gcr.io/kubernetes-e2e-test-images/agnhost:1.0
|
||||
name: agnhost
|
||||
dnsConfig:
|
||||
nameservers:
|
||||
- 1.1.1.1
|
||||
searches:
|
||||
- resolv.conf.local
|
||||
dnsPolicy: None
|
||||
```
|
||||
|
||||
After we've used it to create a pod:
|
||||
|
||||
```console
|
||||
kubectl create -f pod.yaml
|
||||
```
|
||||
|
||||
We can then check the container's output to see what is DNS suffix list the Pod was
|
||||
configured with:
|
||||
|
||||
```console
|
||||
kubectl logs pod/test-agnhost
|
||||
```
|
||||
|
||||
The output will be `resolv.conf.local`, as expected. Alternatively, the Pod could be
|
||||
created with the `pause` argument instead, allowing us execute multiple commands:
|
||||
|
||||
```console
|
||||
kubectl exec test-agnhost -- /agnhost dns-suffix
|
||||
kubectl exec test-agnhost -- /agnhost dns-server-list
|
||||
```
|
||||
|
||||
## Image
|
||||
|
||||
The image can be found at `gcr.io/kubernetes-e2e-test-images/agnhost:1.0` for Linux
|
||||
containers, and `e2eteam/agnhost:1.0` for Windows containers. In the future, the same
|
||||
repository can be used for both OSes.
|
|
@ -23,13 +23,40 @@ import (
|
|||
func main() {
|
||||
cmdDNSSuffix := &cobra.Command{
|
||||
Use: "dns-suffix",
|
||||
Short: "Prints the host's DNS suffix list.",
|
||||
Long: `prints the DNS suffixes of this host.`,
|
||||
Short: "Prints the host's DNS suffix list",
|
||||
Long: `Prints the DNS suffixes of this host.`,
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
Run: printDNSSuffixList,
|
||||
}
|
||||
|
||||
cmdDNSServerList := &cobra.Command{
|
||||
Use: "dns-server-list",
|
||||
Short: "Prints the host's DNS Server list",
|
||||
Long: `Prints the DNS Server list of this host.`,
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
Run: printDNSServerList,
|
||||
}
|
||||
|
||||
cmdEtcHosts := &cobra.Command{
|
||||
Use: "etc-hosts",
|
||||
Short: "Prints the host's /etc/hosts file",
|
||||
Long: `Prints the "hosts" file of this host."`,
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
Run: printHostsFile,
|
||||
}
|
||||
|
||||
cmdPause := &cobra.Command{
|
||||
Use: "pause",
|
||||
Short: "Pauses",
|
||||
Long: `Pauses the execution. Useful for keeping the containers running, so other commands can be executed.`,
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
Run: pause,
|
||||
}
|
||||
|
||||
rootCmd := &cobra.Command{Use: "app"}
|
||||
rootCmd.AddCommand(cmdDNSSuffix)
|
||||
rootCmd.AddCommand(cmdDNSServerList)
|
||||
rootCmd.AddCommand(cmdEtcHosts)
|
||||
rootCmd.AddCommand(cmdPause)
|
||||
rootCmd.Execute()
|
||||
}
|
||||
|
|
|
@ -17,8 +17,14 @@ limitations under the License.
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -27,3 +33,58 @@ func printDNSSuffixList(cmd *cobra.Command, args []string) {
|
|||
dnsSuffixList := getDNSSuffixList()
|
||||
fmt.Println(strings.Join(dnsSuffixList, ","))
|
||||
}
|
||||
|
||||
func printDNSServerList(cmd *cobra.Command, args []string) {
|
||||
dnsServerList := getDNSServerList()
|
||||
fmt.Println(strings.Join(dnsServerList, ","))
|
||||
}
|
||||
|
||||
func printHostsFile(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(readFile(etcHostsFile))
|
||||
}
|
||||
|
||||
func pause(cmd *cobra.Command, args []string) {
|
||||
sigCh := make(chan os.Signal)
|
||||
done := make(chan int, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT)
|
||||
signal.Notify(sigCh, syscall.SIGTERM)
|
||||
signal.Notify(sigCh, syscall.SIGKILL)
|
||||
go func() {
|
||||
sig := <-sigCh
|
||||
switch sig {
|
||||
case syscall.SIGINT:
|
||||
done <- 1
|
||||
os.Exit(1)
|
||||
case syscall.SIGTERM:
|
||||
done <- 2
|
||||
os.Exit(2)
|
||||
case syscall.SIGKILL:
|
||||
done <- 0
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
result := <-done
|
||||
fmt.Printf("exiting %d\n", result)
|
||||
}
|
||||
|
||||
func readFile(fileName string) string {
|
||||
fileData, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return string(fileData)
|
||||
}
|
||||
|
||||
func runCommand(name string, arg ...string) string {
|
||||
var out bytes.Buffer
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.Stdout = &out
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(out.String())
|
||||
}
|
||||
|
|
|
@ -19,22 +19,18 @@ limitations under the License.
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const etcHostsFile = "/etc/hosts"
|
||||
|
||||
// A /etc/resolv.conf file managed by kubelet looks like this:
|
||||
// nameserver DNS_CLUSTER_IP
|
||||
// search test-dns.svc.cluster.local svc.cluster.local cluster.local q53aahaikqaehcai3ylfqdtc5b.bx.internal.cloudapp.net
|
||||
// options ndots:5
|
||||
func getDNSSuffixList() []string {
|
||||
// A /etc/resolv.conf file managed by kubelet looks like this:
|
||||
// nameserver DNS_CLUSTER_IP
|
||||
// search test-dns.svc.cluster.local svc.cluster.local cluster.local q53aahaikqaehcai3ylfqdtc5b.bx.internal.cloudapp.net
|
||||
// options ndots:5
|
||||
|
||||
fileData, err := ioutil.ReadFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(fileData), "\n")
|
||||
fileData := readFile("/etc/resolv.conf")
|
||||
lines := strings.Split(fileData, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "search") {
|
||||
// omit the starting "search".
|
||||
|
@ -44,3 +40,16 @@ func getDNSSuffixList() []string {
|
|||
|
||||
panic("Could not find DNS search list!")
|
||||
}
|
||||
|
||||
func getDNSServerList() []string {
|
||||
fileData := readFile("/etc/resolv.conf")
|
||||
lines := strings.Split(fileData, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "nameserver") {
|
||||
// omit the starting "nameserver".
|
||||
return strings.Split(line, " ")[1:]
|
||||
}
|
||||
}
|
||||
|
||||
panic("Could not find DNS search list!")
|
||||
}
|
||||
|
|
|
@ -17,25 +17,25 @@ limitations under the License.
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const etcHostsFile = "C:/Windows/System32/drivers/etc/hosts"
|
||||
|
||||
func getDNSSuffixList() []string {
|
||||
var out bytes.Buffer
|
||||
cmd := exec.Command("powershell", "-Command", "(Get-DnsClient)[0].SuffixSearchList")
|
||||
cmd.Stdout = &out
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
output := strings.TrimSpace(out.String())
|
||||
output := runCommand("powershell", "-Command", "(Get-DnsClient)[0].SuffixSearchList")
|
||||
if len(output) > 0 {
|
||||
return strings.Split(output, "\r\n")
|
||||
}
|
||||
|
||||
panic("Could not find DNS search list!")
|
||||
}
|
||||
|
||||
func getDNSServerList() []string {
|
||||
output := runCommand("powershell", "-Command", "(Get-DnsClientServerAddress).ServerAddresses")
|
||||
if len(output) > 0 {
|
||||
return strings.Split(output, "\r\n")
|
||||
}
|
||||
|
||||
panic("Could not find DNS Server list!")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue