Commit 6b4c33e0 authored by Addshore's avatar Addshore 🏄
Browse files

Ability to run integration tests locally

parent 92ccce9b
......@@ -9,3 +9,4 @@ internal/mwdd/files/files.go
coverage.txt
coverage.html
coverage.xml
.mediawiki
\ No newline at end of file
image: docker:19.03.12
image: docker:19.03.15
variables:
# When you use the dind service, you must instruct Docker to talk with
......@@ -33,10 +33,10 @@ stages:
cache:
- key: mediawiki
paths:
- mediawiki
- .mediawiki
services:
- name: docker:19.03.12-dind
- name: docker:19.03.15-dind
test:
stage: test
......@@ -86,7 +86,7 @@ integration-general:
- build
parallel:
matrix:
- TEST: general-commands.sh
- TEST: test-general-commands.sh
before_script:
# libc6-compat needed because https://stackoverflow.com/questions/36279253/go-compiled-binary-wont-run-in-an-alpine-docker-container-on-ubuntu-host
- apk add --no-cache libc6-compat bash
......@@ -100,14 +100,13 @@ integration-docker:
- build
parallel:
matrix:
- TEST: docker-mw-extra-commands.sh
- TEST: docker-mw-install-all-the-dbs.sh
- TEST: docker-mw-mysql-suspend-resume-destroy.sh
- TEST: test-docker-general.sh
- TEST: test-docker-mw-all-dbs.sh
- TEST: test-docker-mw-mysql-cycle.sh
before_script:
# libc6-compat needed because https://stackoverflow.com/questions/36279253/go-compiled-binary-wont-run-in-an-alpine-docker-container-on-ubuntu-host
- apk add --no-cache libc6-compat bash docker-compose curl
- ./tests/cache-mediawiki.sh
- ./tests/setup.sh
- apk add --no-cache libc6-compat bash docker-compose curl tar
- ./tests/pretest-mediawiki.sh
script:
- ./tests/$TEST
......
......@@ -19,7 +19,7 @@ If this doesn't free up enough space the next step would be to nuke the registry
### Make a machine
Make a VM, such as `gitlab-runner-addshore-1001.integration.eqiad1.wikimedia.cloud`
Make a VM, such as `gitlab-runner-addshore-1004.integration.eqiad1.wikimedia.cloud`
### Install docker
......@@ -47,6 +47,7 @@ sudo apt-get install docker-ce docker-ce-cli containerd.io
```sh
curl -LJO "https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_amd64.deb"
sudo dpkg -i gitlab-runner_amd64.deb
rm gitlab-runner_amd64.deb
```
### Register the runner
......
......@@ -64,6 +64,28 @@ No naming structured is enforced in CI but a convention exists that should be fo
- Complex sub commands can be split out into their own file.
- This is a recursive solution.
## CI & Integration tests
This repository has continious integration setup on Gitlab.
You can read more in the [CI README](./CI.md).
You can also choose to run the integration tests locally.
```sh
./tests/test-general-commands.sh
```
Or for the dev environment
```sh
./tests/test-docker-general.sh
./tests/test-docker-mw-all-dbs.sh
./tests/test-docker-mw-mysql-cycle.sh
```
These tests should clean up after themselves.
If you run into issues you might find `./tests/destroy.sh` useful.
## Releasing
Releases are automatically built and published by Gitlab CI after pushing a tag.
......
......@@ -32,6 +32,7 @@ var mwddEnvSetCmd = cmd.EnvSet(mwddEnvDirectory)
var mwddEnvGetCmd = cmd.EnvGet(mwddEnvDirectory)
var mwddEnvListCmd = cmd.EnvList(mwddEnvDirectory)
var mwddEnvWhereCmd = cmd.EnvWhere(mwddEnvDirectory)
var mwddEnvClearCmd = cmd.EnvClear(mwddEnvDirectory)
func init() {
mwddCmd.AddCommand(mwddEnvCmd)
......@@ -41,4 +42,5 @@ func init() {
mwddEnvCmd.AddCommand(mwddEnvGetCmd)
mwddEnvCmd.AddCommand(mwddEnvListCmd)
mwddEnvCmd.AddCommand(mwddEnvDeleteCmd)
mwddEnvCmd.AddCommand(mwddEnvClearCmd)
}
......@@ -19,6 +19,7 @@ package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"gitlab.wikimedia.org/releng/cli/internal/mwdd"
......@@ -49,14 +50,18 @@ var mwddHostsAddCmd = &cobra.Command{
),
)
if save.Success {
fmt.Println("Hosts file updated!")
fmt.Println("Hosts file " + save.WriteFile + " updated!")
} else {
fmt.Println("Could not save your hosts file.")
fmt.Println("You can return with sudo.")
fmt.Println("Or edit the hosts fiel yourself.")
fmt.Println("Temporary file: " + save.TmpFile)
fmt.Println("Or edit the hosts file yourself.")
fmt.Println("Temporary file: " + save.WriteFile)
fmt.Println("")
fmt.Println(save.Content)
// Hack around https://phabricator.wikimedia.org/T292909
if os.Getenv("MWCLI_CONTEXT_TEST") == "" {
os.Exit(1)
}
}
},
}
......@@ -67,14 +72,31 @@ var mwddHostsRemoveCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
save := hosts.RemoveHostsWithSuffix("mwdd.localhost")
if save.Success {
fmt.Println("Hosts file updated!")
fmt.Println("Hosts file " + save.WriteFile + " updated!")
} else {
fmt.Println("Could not save your hosts file.")
fmt.Println("You can return with sudo.")
fmt.Println("Or edit the hosts fiel yourself.")
fmt.Println("Temporary file: " + save.TmpFile)
fmt.Println("Or edit the hosts file yourself.")
fmt.Println("Temporary file: " + save.WriteFile)
fmt.Println("")
fmt.Println(save.Content)
// Hack around https://phabricator.wikimedia.org/T292909
if os.Getenv("MWCLI_CONTEXT_TEST") == "" {
os.Exit(1)
}
}
},
}
var mwddHostsWritableCmd = &cobra.Command{
Use: "writable",
Short: "Checks if you can write to the needed hosts file",
Run: func(cmd *cobra.Command, args []string) {
if hosts.Writable() {
fmt.Println("Hosts file writable")
} else {
fmt.Println("Hosts file not writable")
os.Exit(1)
}
},
}
......@@ -83,4 +105,5 @@ func init() {
mwddCmd.AddCommand(mwddHostsCmd)
mwddHostsCmd.AddCommand(mwddHostsAddCmd)
mwddHostsCmd.AddCommand(mwddHostsRemoveCmd)
mwddHostsCmd.AddCommand(mwddHostsWritableCmd)
}
......@@ -95,3 +95,18 @@ func EnvWhere(directory func() string) *cobra.Command {
},
}
}
/*EnvClear env clear command*/
func EnvClear(directory func() string) *cobra.Command {
return &cobra.Command{
Use: "clear",
Short: "Clears all values from the .env file",
Run: func(cmd *cobra.Command, args []string) {
file := dotenv.FileForDirectory(directory())
for name := range file.List() {
file.Delete(name)
}
fmt.Println("Cleared .env file")
},
}
}
......@@ -30,9 +30,17 @@ import (
/*MWDD representation of a mwdd v2 setup*/
type MWDD string
func mwddContext() string {
_, inGitlabCi := os.LookupEnv("GITLAB_CI")
if !inGitlabCi && os.Getenv("MWCLI_CONTEXT_TEST") != "" {
return "test"
}
return "default"
}
/*DefaultForUser returns the default mwdd working directory for the user*/
func DefaultForUser() MWDD {
return MWDD(mwddUserDirectory() + string(os.PathSeparator) + "default")
return MWDD(mwddUserDirectory() + string(os.PathSeparator) + mwddContext())
}
func mwddUserDirectory() string {
......@@ -74,7 +82,7 @@ func (m MWDD) Directory() string {
/*DockerComposeProjectName the name of the docker-compose project*/
func (m MWDD) DockerComposeProjectName() string {
return "mwcli-mwdd-default"
return "mwcli-mwdd-" + mwddContext()
}
/*Env ...*/
......
......@@ -30,15 +30,29 @@ var hostsTmpPrefix = "mwcli-hosts-"
/*Save result of saving the hosts file*/
type Save struct {
Success bool
Content string
TmpFile string
Success bool
Content string
WriteFile string
}
/*AddHosts attempts to add requested hosts to the system hosts file, and gives you the new content, a tmp file and success*/
func AddHosts(toAdd []string) Save {
hosts := hosts()
hosts.AddHosts("127.0.0.1", toAdd)
serviceIP := "127.0.0.1"
_, inGitlabCi := os.LookupEnv("GITLAB_CI")
if inGitlabCi {
// Localhost does not refer to our services in Gitlab CI, docker does
// https://gitlab.com/gitlab-org/gitlab/-/issues/34814#note_426362459
serviceIP = IPv4("docker")
}
// TODO if verbose..
//fmt.Println("Adding hosts:", toAdd)
hosts.AddHosts(serviceIP, toAdd)
// TODO when the library supports it do ipv6 too https://github.com/txn2/txeh/issues/15
//hosts.AddHosts("::1", toAdd)
return save(hosts)
}
......@@ -49,6 +63,20 @@ func RemoveHostsWithSuffix(hostSuffix string) Save {
return save(hosts)
}
/*Writable is the hosts file writable*/
func Writable() bool {
return fileIsWritable(hosts().HostsConfig.WriteFilePath)
}
func fileIsWritable(filePath string) bool {
file, err := os.OpenFile(filePath, os.O_WRONLY, 0666)
if err != nil {
return false
}
file.Close()
return true
}
func tmpFile() string {
tmpFile, err := ioutil.TempFile(os.TempDir(), hostsTmpPrefix)
if err != nil {
......@@ -92,15 +120,15 @@ func save(hosts *txeh.Hosts) Save {
panic(err)
}
return Save{
Success: false,
Content: hosts.RenderHostsFile(),
TmpFile: tmpFile,
Success: false,
Content: hosts.RenderHostsFile(),
WriteFile: tmpFile,
}
}
return Save{
Success: true,
Content: hosts.RenderHostsFile(),
TmpFile: hosts.WriteFilePath,
Success: true,
Content: hosts.RenderHostsFile(),
WriteFile: hosts.WriteFilePath,
}
}
......@@ -40,6 +40,7 @@ func writeContentToTmpFile(content string) string {
}
func TestAddHosts(t *testing.T) {
ipv4AddressOverride = "127.0.0.1"
type args struct {
toAdd []string
}
......@@ -56,9 +57,9 @@ func TestAddHosts(t *testing.T) {
toAdd: []string{"1.mwcli.test"},
},
want: Save{
Success: true,
Content: "127.0.0.1 1.mwcli.test\n",
TmpFile: "",
Success: true,
Content: "127.0.0.1 1.mwcli.test\n",
WriteFile: "",
},
},
{
......@@ -68,9 +69,9 @@ func TestAddHosts(t *testing.T) {
toAdd: []string{"1.mwcli.test", "2.mwcli.test"},
},
want: Save{
Success: true,
Content: "127.0.0.1 1.mwcli.test 2.mwcli.test\n",
TmpFile: "",
Success: true,
Content: "127.0.0.1 1.mwcli.test 2.mwcli.test\n",
WriteFile: "",
},
},
{
......@@ -80,9 +81,9 @@ func TestAddHosts(t *testing.T) {
toAdd: []string{"1.mwcli.test", "2.mwcli.test"},
},
want: Save{
Success: true,
Content: "127.0.0.1 iam.localhost 1.mwcli.test 2.mwcli.test\n",
TmpFile: "",
Success: true,
Content: "127.0.0.1 iam.localhost 1.mwcli.test 2.mwcli.test\n",
WriteFile: "",
},
},
{
......@@ -92,9 +93,9 @@ func TestAddHosts(t *testing.T) {
toAdd: []string{"1.mwcli.test", "2.mwcli.test"},
},
want: Save{
Success: true,
Content: "123.123.111.111 iam.not.localhost\n127.0.0.1 1.mwcli.test 2.mwcli.test\n",
TmpFile: "",
Success: true,
Content: "123.123.111.111 iam.not.localhost\n127.0.0.1 1.mwcli.test 2.mwcli.test\n",
WriteFile: "",
},
},
}
......@@ -103,7 +104,7 @@ func TestAddHosts(t *testing.T) {
// Setup a test file
testFile := writeContentToTmpFile(tt.startingContent)
hostsFile = testFile
tt.want.TmpFile = testFile
tt.want.WriteFile = testFile
// Perform the test!
if got := AddHosts(tt.args.toAdd); !reflect.DeepEqual(got, tt.want) {
......@@ -114,6 +115,7 @@ func TestAddHosts(t *testing.T) {
}
func TestRemoveHostsWithSuffix(t *testing.T) {
ipv4AddressOverride = "127.0.0.1"
type args struct {
hostSuffix string
}
......@@ -130,9 +132,9 @@ func TestRemoveHostsWithSuffix(t *testing.T) {
hostSuffix: "mwcli.test",
},
want: Save{
Success: true,
Content: singleLocalHost,
TmpFile: "",
Success: true,
Content: singleLocalHost,
WriteFile: "",
},
},
{
......@@ -142,9 +144,9 @@ func TestRemoveHostsWithSuffix(t *testing.T) {
hostSuffix: "mwcli.test",
},
want: Save{
Success: true,
Content: "",
TmpFile: "",
Success: true,
Content: "",
WriteFile: "",
},
},
{
......@@ -154,9 +156,9 @@ func TestRemoveHostsWithSuffix(t *testing.T) {
hostSuffix: "mwcli.test",
},
want: Save{
Success: true,
Content: singleLocalHost,
TmpFile: "",
Success: true,
Content: singleLocalHost,
WriteFile: "",
},
},
}
......@@ -165,7 +167,7 @@ func TestRemoveHostsWithSuffix(t *testing.T) {
// Setup a test file
testFile := writeContentToTmpFile(tt.startingContent)
hostsFile = testFile
tt.want.TmpFile = testFile
tt.want.WriteFile = testFile
// Perform the test!
if got := RemoveHostsWithSuffix(tt.args.hostSuffix); !reflect.DeepEqual(got, tt.want) {
......
/*Package hosts in internal utils is functionality for interacting with an etc hosts file
Copyright © 2020 Addshore
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package hosts
import (
"net"
)
var ipv4AddressOverride = ""
/*IPs ... */
func IPs(host string) ([]string, error) {
// Resolver https://gist.github.com/aojea/94f6f483173641647c731f582e52f0b0#file-resolve_localhost-go-L11
addrs, err := net.LookupHost(host)
//fmt.Println("net.LookupHost addrs:", addrs, "err:", err)
return addrs, err
}
func addressType(ip string) int {
if net.ParseIP(ip) == nil {
// Invalid address type..
return 0
}
for i := 0; i < len(ip); i++ {
switch ip[i] {
case '.':
return 4
case ':':
return 6
}
}
return -1
}
func getFirstOfType(addrs []string, t int) *string {
for _, a := range addrs {
if addressType(a) == t {
return &a
}
}
return nil
}
/*IPv4 ...*/
func IPv4(host string) string {
if ipv4AddressOverride != "" {
return ipv4AddressOverride
}
addrs, _ := IPs(host)
return *getFirstOfType(addrs, 4)
}
/*IPv6 ...*/
func IPv6(host string) string {
addrs, _ := IPs(host)
return *getFirstOfType(addrs, 6)
}
/*Package hosts in internal utils is functionality for interacting with an etc hosts file
Copyright © 2020 Addshore
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package hosts
import (
"testing"
)
func TestLocalhostIps(t *testing.T) {
tests := []struct {
name string
}{
{
name: "localhost has 127.0.0.1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, _ := IPs("localhost")
if !splitContains(got, "127.0.0.1") {
t.Errorf("LocalhostIps() = doesn't container 127.0.0.1 and probably should, got: %v", got)
return
}
})
}
}
func splitContains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
......@@ -9,7 +9,6 @@ services:
depends_on:
- dps
- nginx-proxy
hostname: adminer.mwdd
dns:
- 10.0.0.10
networks:
......
......@@ -6,7 +6,6 @@ services:
image: defreitas/dns-proxy-server:2.19.0
volumes:
- /var/run/docker.sock:/var/run/docker.sock
hostname: dps.mwdd.localhost
networks:
dps:
ipv4_address: 10.0.0.10
......@@ -15,17 +14,15 @@ services:
image: jwilder/nginx-proxy:0.9
environment:
- VIRTUAL_HOST=proxy.mwdd.localhost,proxy.mwdd
- VIRTUAL_PORT=${PORT}
- HOSTNAMES=.mediawiki.mwdd.localhost,.mediawiki.mwdd # wildcard name resolution, thanks to DPS
- HTTP_PORT=${PORT} # internal port
ports:
- "${PORT}:${PORT}"
depends_on:
- dps
hostname: proxy.mwdd
dns:
- 10.0.0.10
dns_search:
- mwdd
networks:
- dps
volumes:
......
......@@ -5,7 +5,6 @@ services:
image: graphiteapp/graphite-statsd:1.1.8-1
environment:
- VIRTUAL_HOST=graphite.mwdd.localhost,graphite.mwdd
hostname: graphite.mwdd
depends_on:
- nginx-proxy
dns:
......
......@@ -20,13 +20,10 @@ services:
- COMPOSER_CACHE_DIR=/.composer/cache
- XDEBUG_CONFIG=${MEDIAWIKI_XDEBUG_CONFIG:-}
- XDEBUG_MODE=${MEDIAWIKI_XDEBUG_MODE:-develop,debug}
hostname: mediawiki.mwdd
depends_on:
- mediawiki-web
dns:
- 10.0.0.10
dns_search:
- mwdd
networks:
- dps
......@@ -39,7 +36,6 @@ services:
environment:
- VIRTUAL_HOST=*.mediawiki.mwdd.localhost,*.mediawiki.mwdd
- VIRTUAL_PORT=8080
hostname: mediawiki-web.mwdd
depends_on:
- nginx-proxy
dns:
......
......@@ -6,7 +6,6 @@ services:
# TODO think about this and alter how we provide the cli tools?
#image: "${MEMCACHED_IMAGE:-docker-registry.wikimedia.org/memcached:1.6.6-1-20211003}"
image: "${MEMCACHED_IMAGE:-memcached:1.6}"
hostname: memcached.mwdd
dns:
- 10.0.0.10
networks:
......