Skip to content

Commit

Permalink
/etc/hosts precondition
Browse files Browse the repository at this point in the history
  • Loading branch information
enr committed Dec 19, 2022
1 parent 30bd5b3 commit a2d9e3a
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 5 deletions.
5 changes: 3 additions & 2 deletions docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ Any unit is ran if all its preconditions are satisfied.

Available preconditions:

- OS
- Runp version
- OS: unit is ran if OS is the specified one.
- Runp version: unit is ran if Runp version is in range.
- Hosts: unit is ran if `/etc/hosts` contains the given mapping.
**Runpfile composition**

Expand Down
11 changes: 10 additions & 1 deletion examples/Runpfile-preconditions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ units:
- linux
- darwin
host:
command: ls -al {{vars runp_root}}
command: ls -al {{vars runp_root}}
etchosts:
description: Unit with /etc/hosts precondition
preconditions:
hosts:
contains:
127.0.1.1:
- hop
host:
command: echo file /etc/hosts contains mapping "127.0.1.1 hop"
6 changes: 4 additions & 2 deletions lib/core/runpfile_preconditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@ type PreconditionVerifyResult struct {

// Preconditions ...
type Preconditions struct {
Os OsPrecondition
Runp RunpVersionPrecondition
Os OsPrecondition
Runp RunpVersionPrecondition
Hosts EtcHostsPrecondition
}

// Verify ...
func (p *Preconditions) Verify() PreconditionVerifyResult {
preconditions := []Precondition{
&p.Os,
&p.Runp,
&p.Hosts,
}
var vr PreconditionVerifyResult
res := PreconditionVerifyResult{Vote: Proceed, Reasons: []string{}}
Expand Down
86 changes: 86 additions & 0 deletions lib/core/runpfile_preconditions_etchosts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package core

import (
"fmt"
"os"
"runtime"
"strings"
)

var defaultEtcHostsReader = func() ([]byte, error) {
ehp := `/etc/hosts`
if runtime.GOOS == "windows" {
ehp = `c:\Windows\System32\Drivers\etc\hosts`
}
return os.ReadFile(ehp)
}

// EtcHostsPrecondition verify /etc/hosts contains given host.
type EtcHostsPrecondition struct {
Contains map[string][]string

etcHostsReader func() ([]byte, error)
}

// Verify ...
func (p *EtcHostsPrecondition) Verify() PreconditionVerifyResult {
if p.etcHostsReader == nil {
p.etcHostsReader = defaultEtcHostsReader
}
etcHosts, err := p.etcHostsReader()
if err != nil {
return PreconditionVerifyResult{
Vote: Stop,
Reasons: []string{err.Error()},
}
}
hosts, err := parseEtcHosts(etcHosts)
for k, values := range p.Contains {
mapped, exist := hosts[k]
if !exist {
return PreconditionVerifyResult{
Vote: Stop,
Reasons: []string{fmt.Sprintf(`No mapping found for "%s"`, k)},
}
}
for _, v := range values {
if sliceContains(mapped, v) {
continue
}
return PreconditionVerifyResult{
Vote: Stop,
Reasons: []string{fmt.Sprintf(`Hosts mapping "%s" %v does not contain "%s"`, k, mapped, v)},
}
}
}
return PreconditionVerifyResult{
Vote: Proceed,
Reasons: []string{},
}
}

// IsSet ...
func (p *EtcHostsPrecondition) IsSet() bool {
return len(p.Contains) > 0
}

func parseEtcHosts(hostsFileContent []byte) (map[string][]string, error) {
hostsMap := map[string][]string{}
for _, line := range strings.Split(strings.Trim(string(hostsFileContent), " \t\r\n"), "\n") {
line = strings.Replace(strings.Trim(line, " \t"), "\t", " ", -1)
if len(line) == 0 || line[0] == ';' || line[0] == '#' {
continue
}
pieces := strings.SplitN(line, " ", 2)
if len(pieces) > 1 && len(pieces[0]) > 0 {
if names := strings.Fields(pieces[1]); len(names) > 0 {
if _, ok := hostsMap[pieces[0]]; ok {
hostsMap[pieces[0]] = append(hostsMap[pieces[0]], names...)
} else {
hostsMap[pieces[0]] = names
}
}
}
}
return hostsMap, nil
}
96 changes: 96 additions & 0 deletions lib/core/runpfile_preconditions_etchosts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package core

import (
"testing"
)

const etcHosts01 = `
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host
# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost
# Added by Docker Desktop
# 192.168.22.134 host.docker.internal
# 192.168.22.134 gateway.docker.internal
127.0.0.1 private-host-01
127.0.0.1 private-host-02
# To allow the same kube context to work on the host and the container:
# 127.0.0.1 kubernetes.docker.internal
# End of section
127.0.0.1 domain-01
192.168.2.2 domain-03
102.54.99.99 rhino.acme.com
`

func TestEtcHostsOk(t *testing.T) {

wanted := map[string][]string{}
wanted[`127.0.0.1`] = []string{
`private-host-01`,
`private-host-02`,
`domain-01`,
}
wanted[`192.168.2.2`] = []string{
`domain-03`,
}
wanted[`102.54.99.99`] = []string{
`rhino.acme.com`,
}

ui = CreateMainLogger(" ", 6, "%s> ", true, false)

sut := &EtcHostsPrecondition{
Contains: wanted,
etcHostsReader: func() ([]byte, error) {
return []byte(etcHosts01), nil
},
}
res := sut.Verify()
if res.Vote != Proceed {
t.Errorf("Expected Proceed but got %v: %v", res.Vote, res.Reasons)
}
}

func TestEtcHostsKo(t *testing.T) {

wanted := map[string][]string{}
wanted[`127.0.0.1`] = []string{
`no.such.host`,
// correct but not enough to satisfy precondition
`domain-01`,
}
wanted[`192.168.22.134`] = []string{
`host.docker.internal`,
}

ui = CreateMainLogger(" ", 6, "%s> ", true, false)

sut := &EtcHostsPrecondition{
Contains: wanted,
etcHostsReader: func() ([]byte, error) {
return []byte(etcHosts01), nil
},
}
res := sut.Verify()
if res.Vote != Stop {
t.Errorf("Expected Stop but got %v", res.Vote)
}
}

0 comments on commit a2d9e3a

Please sign in to comment.