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() {
|
func main() {
|
||||||
cmdDNSSuffix := &cobra.Command{
|
cmdDNSSuffix := &cobra.Command{
|
||||||
Use: "dns-suffix",
|
Use: "dns-suffix",
|
||||||
Short: "Prints the host's DNS suffix list.",
|
Short: "Prints the host's DNS suffix list",
|
||||||
Long: `prints the DNS suffixes of this host.`,
|
Long: `Prints the DNS suffixes of this host.`,
|
||||||
Args: cobra.MaximumNArgs(0),
|
Args: cobra.MaximumNArgs(0),
|
||||||
Run: printDNSSuffixList,
|
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 := &cobra.Command{Use: "app"}
|
||||||
rootCmd.AddCommand(cmdDNSSuffix)
|
rootCmd.AddCommand(cmdDNSSuffix)
|
||||||
|
rootCmd.AddCommand(cmdDNSServerList)
|
||||||
|
rootCmd.AddCommand(cmdEtcHosts)
|
||||||
|
rootCmd.AddCommand(cmdPause)
|
||||||
rootCmd.Execute()
|
rootCmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,14 @@ limitations under the License.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -27,3 +33,58 @@ func printDNSSuffixList(cmd *cobra.Command, args []string) {
|
||||||
dnsSuffixList := getDNSSuffixList()
|
dnsSuffixList := getDNSSuffixList()
|
||||||
fmt.Println(strings.Join(dnsSuffixList, ","))
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
"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 {
|
func getDNSSuffixList() []string {
|
||||||
// A /etc/resolv.conf file managed by kubelet looks like this:
|
fileData := readFile("/etc/resolv.conf")
|
||||||
// nameserver DNS_CLUSTER_IP
|
lines := strings.Split(fileData, "\n")
|
||||||
// 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")
|
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if strings.HasPrefix(line, "search") {
|
if strings.HasPrefix(line, "search") {
|
||||||
// omit the starting "search".
|
// omit the starting "search".
|
||||||
|
@ -44,3 +40,16 @@ func getDNSSuffixList() []string {
|
||||||
|
|
||||||
panic("Could not find DNS search list!")
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const etcHostsFile = "C:/Windows/System32/drivers/etc/hosts"
|
||||||
|
|
||||||
func getDNSSuffixList() []string {
|
func getDNSSuffixList() []string {
|
||||||
var out bytes.Buffer
|
output := runCommand("powershell", "-Command", "(Get-DnsClient)[0].SuffixSearchList")
|
||||||
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())
|
|
||||||
if len(output) > 0 {
|
if len(output) > 0 {
|
||||||
return strings.Split(output, "\r\n")
|
return strings.Split(output, "\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("Could not find DNS search list!")
|
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