From fb4d2d1272ae3f7a00bb8cf5af42446abbd1bded Mon Sep 17 00:00:00 2001 From: afeiszli Date: Tue, 19 Oct 2021 15:53:01 -0400 Subject: [PATCH] change version of ubuntu --- .github/workflows/test.yml | 14 +- controllers/common_test.go | 2 +- controllers/dnsHttpController_test.go | 292 +++++++++++++++--- controllers/userHttpController.go | 16 +- controllers/userHttpController_test.go | 63 ++++ go.mod | 2 +- go.sum | 17 + kube/helm/README.md | 130 -------- kube/helm/netmaker/Chart.lock | 6 - kube/helm/netmaker/Chart.yaml | 29 -- kube/helm/netmaker/README.md | 57 ---- .../netmaker/charts/postgresql-ha-7.11.0.tgz | Bin 53745 -> 0 bytes kube/helm/netmaker/templates/NOTES.txt | 22 -- kube/helm/netmaker/templates/_helpers.tpl | 70 ----- kube/helm/netmaker/templates/coredns.yaml | 85 ----- kube/helm/netmaker/templates/ingress.yaml | 236 -------------- .../templates/netmaker-statefulset.yaml | 133 -------- .../templates/netmaker-ui-deployment.yaml | 25 -- .../netmaker/templates/serviceaccount.yaml | 12 - kube/helm/netmaker/templates/services.yaml | 72 ----- .../templates/tests/test-connection.yaml | 15 - kube/helm/netmaker/values.yaml | 124 -------- logic/util.go | 1 + netclient/command/commands.go | 2 +- netclient/daemon/systemd.go | 7 + netclient/functions/common.go | 28 +- netclient/functions/list.go | 128 ++++++++ netclient/main.go | 7 +- scripts/netclient-install.sh | 2 +- 29 files changed, 497 insertions(+), 1100 deletions(-) delete mode 100644 kube/helm/README.md delete mode 100644 kube/helm/netmaker/Chart.lock delete mode 100644 kube/helm/netmaker/Chart.yaml delete mode 100644 kube/helm/netmaker/README.md delete mode 100644 kube/helm/netmaker/charts/postgresql-ha-7.11.0.tgz delete mode 100644 kube/helm/netmaker/templates/NOTES.txt delete mode 100644 kube/helm/netmaker/templates/_helpers.tpl delete mode 100644 kube/helm/netmaker/templates/coredns.yaml delete mode 100644 kube/helm/netmaker/templates/ingress.yaml delete mode 100644 kube/helm/netmaker/templates/netmaker-statefulset.yaml delete mode 100644 kube/helm/netmaker/templates/netmaker-ui-deployment.yaml delete mode 100644 kube/helm/netmaker/templates/serviceaccount.yaml delete mode 100644 kube/helm/netmaker/templates/services.yaml delete mode 100644 kube/helm/netmaker/templates/tests/test-connection.yaml delete mode 100644 kube/helm/netmaker/values.yaml create mode 100644 netclient/functions/list.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d1b7151f..bc3c0509 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,14 +6,8 @@ on: jobs: tests: env: - DATABASE: rqlite - runs-on: ubuntu-latest - services: - rqlite: - image: rqlite/rqlite - ports: - - 4001:4001 - - 4002:4002 + DATABASE: sqlite + runs-on: ubuntu-20.04 steps: - name: Checkout uses: actions/checkout@v2 @@ -21,5 +15,5 @@ jobs: run: | go test -p 1 ./... -v env: - DATABASE: rqlite - CLIENT_MODE: "off" \ No newline at end of file + DATABASE: sqlite + CLIENT_MODE: "off" diff --git a/controllers/common_test.go b/controllers/common_test.go index 6e026c1a..f00f8e86 100644 --- a/controllers/common_test.go +++ b/controllers/common_test.go @@ -124,7 +124,7 @@ func TestSetNetworkNodesLastModified(t *testing.T) { } func createTestNode() models.Node { - createnode := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Endpoint: "10.0.0.1", MacAddress: "01:02:03:04:05:06", Password: "password", Network: "skynet"} + createnode := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Name: "testnode", Endpoint: "10.0.0.1", MacAddress: "01:02:03:04:05:06", Password: "password", Network: "skynet"} node, _ := logic.CreateNode(createnode, "skynet") return node } diff --git a/controllers/dnsHttpController_test.go b/controllers/dnsHttpController_test.go index db6a7443..d7849321 100644 --- a/controllers/dnsHttpController_test.go +++ b/controllers/dnsHttpController_test.go @@ -1,100 +1,309 @@ package controller import ( + "io/ioutil" + "os" "testing" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/dnslogic" + "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" "github.com/stretchr/testify/assert" ) +func TestGetAllDNS(t *testing.T) { + database.InitializeDatabase() + deleteAllDNS(t) + deleteAllNetworks() + createNet() + t.Run("NoEntries", func(t *testing.T) { + entries, err := GetAllDNS() + assert.Nil(t, err) + assert.Equal(t, []models.DNSEntry(nil), entries) + }) + t.Run("OneEntry", func(t *testing.T) { + entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"} + CreateDNS(entry) + entries, err := GetAllDNS() + assert.Nil(t, err) + assert.Equal(t, 1, len(entries)) + }) + t.Run("MultipleEntry", func(t *testing.T) { + entry := models.DNSEntry{"10.0.0.7", "anotherhost", "skynet"} + CreateDNS(entry) + entries, err := GetAllDNS() + assert.Nil(t, err) + assert.Equal(t, 2, len(entries)) + }) +} + func TestGetNodeDNS(t *testing.T) { database.InitializeDatabase() + deleteAllDNS(t) deleteAllNetworks() createNet() - createTestNode() - dns, err := GetNodeDNS("skynet") - assert.Nil(t, err) - t.Log(dns) + t.Run("NoNodes", func(t *testing.T) { + dns, err := GetNodeDNS("skynet") + assert.EqualError(t, err, "could not find any records") + assert.Equal(t, []models.DNSEntry(nil), dns) + }) + t.Run("NodeExists", func(t *testing.T) { + createTestNode() + dns, err := GetNodeDNS("skynet") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1", dns[0].Address) + }) + t.Run("MultipleNodes", func(t *testing.T) { + createnode := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Endpoint: "10.100.100.3", MacAddress: "01:02:03:04:05:07", Password: "password", Network: "skynet"} + _, err := logic.CreateNode(createnode, "skynet") + assert.Nil(t, err) + dns, err := GetNodeDNS("skynet") + assert.Nil(t, err) + assert.Equal(t, 2, len(dns)) + }) } func TestGetCustomDNS(t *testing.T) { - t.Skip() database.InitializeDatabase() + deleteAllDNS(t) deleteAllNetworks() - createNet() - createTestNode() - dns, err := dnslogic.GetCustomDNS("skynet") - assert.Nil(t, err) - t.Log(dns) + t.Run("NoNetworks", func(t *testing.T) { + dns, err := dnslogic.GetCustomDNS("skynet") + assert.EqualError(t, err, "could not find any records") + assert.Equal(t, []models.DNSEntry(nil), dns) + }) + t.Run("NoNodes", func(t *testing.T) { + createNet() + dns, err := dnslogic.GetCustomDNS("skynet") + assert.EqualError(t, err, "could not find any records") + assert.Equal(t, []models.DNSEntry(nil), dns) + }) + t.Run("NodeExists", func(t *testing.T) { + createTestNode() + dns, err := dnslogic.GetCustomDNS("skynet") + assert.EqualError(t, err, "could not find any records") + assert.Equal(t, 0, len(dns)) + }) + t.Run("EntryExist", func(t *testing.T) { + entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"} + CreateDNS(entry) + dns, err := dnslogic.GetCustomDNS("skynet") + assert.Nil(t, err) + assert.Equal(t, 1, len(dns)) + }) + t.Run("MultipleEntries", func(t *testing.T) { + entry := models.DNSEntry{"10.0.0.4", "host4", "skynet"} + CreateDNS(entry) + dns, err := dnslogic.GetCustomDNS("skynet") + assert.Nil(t, err) + assert.Equal(t, 2, len(dns)) + }) } + func TestGetDNSEntryNum(t *testing.T) { database.InitializeDatabase() + deleteAllDNS(t) deleteAllNetworks() createNet() - createTestNode() - num, err := GetDNSEntryNum("myhost", "skynet") - assert.Nil(t, err) - t.Log(num) + t.Run("NoNodes", func(t *testing.T) { + num, err := GetDNSEntryNum("myhost", "skynet") + assert.Nil(t, err) + assert.Equal(t, 0, num) + }) + t.Run("NodeExists", func(t *testing.T) { + entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"} + _, err := CreateDNS(entry) + assert.Nil(t, err) + num, err := GetDNSEntryNum("newhost", "skynet") + assert.Nil(t, err) + assert.Equal(t, 1, num) + }) } func TestGetDNS(t *testing.T) { database.InitializeDatabase() + deleteAllDNS(t) deleteAllNetworks() - dns, err := dnslogic.GetDNS("skynet") - assert.Nil(t, err) - t.Log(dns) + createNet() + t.Run("NoEntries", func(t *testing.T) { + dns, err := dnslogic.GetDNS("skynet") + assert.Nil(t, err) + assert.Nil(t, dns) + }) + t.Run("CustomDNSExists", func(t *testing.T) { + entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"} + _, err := CreateDNS(entry) + assert.Nil(t, err) + dns, err := dnslogic.GetDNS("skynet") + t.Log(dns) + assert.Nil(t, err) + assert.NotNil(t, dns) + assert.Equal(t, "skynet", dns[0].Network) + assert.Equal(t, 1, len(dns)) + }) + t.Run("NodeExists", func(t *testing.T) { + deleteAllDNS(t) + createTestNode() + dns, err := dnslogic.GetDNS("skynet") + assert.Nil(t, err) + assert.NotNil(t, dns) + assert.Equal(t, "skynet", dns[0].Network) + assert.Equal(t, 1, len(dns)) + }) + t.Run("NodeAndCustomDNS", func(t *testing.T) { + entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"} + _, err := CreateDNS(entry) + dns, err := dnslogic.GetDNS("skynet") + t.Log(dns) + assert.Nil(t, err) + assert.NotNil(t, dns) + assert.Equal(t, "skynet", dns[0].Network) + assert.Equal(t, "skynet", dns[1].Network) + assert.Equal(t, 2, len(dns)) + }) } + func TestCreateDNS(t *testing.T) { database.InitializeDatabase() - deleteAllNetworks() deleteAllDNS(t) + deleteAllNetworks() createNet() - //dns, err := GetDNS("skynet") - //assert.Nil(t, err) - //for _, entry := range dns { - // _, _ = DeleteDNS(entry.Name, "skynet") - //} entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"} - err := ValidateDNSCreate(entry) - assert.Nil(t, err) - if err != nil { - t.Log(err) - } dns, err := CreateDNS(entry) assert.Nil(t, err) - t.Log(dns) + assert.Equal(t, "newhost", dns.Name) } + +func TestSetDNS(t *testing.T) { + database.InitializeDatabase() + deleteAllDNS(t) + deleteAllNetworks() + t.Run("NoNetworks", func(t *testing.T) { + err := dnslogic.SetDNS() + assert.Nil(t, err) + info, err := os.Stat("./config/dnsconfig/netmaker.hosts") + assert.Nil(t, err) + assert.False(t, info.IsDir()) + assert.Equal(t, int64(0), info.Size()) + }) + t.Run("NoEntries", func(t *testing.T) { + createNet() + err := dnslogic.SetDNS() + assert.Nil(t, err) + info, err := os.Stat("./config/dnsconfig/netmaker.hosts") + assert.Nil(t, err) + assert.False(t, info.IsDir()) + assert.Equal(t, int64(0), info.Size()) + }) + t.Run("NodeExists", func(t *testing.T) { + createTestNode() + err := dnslogic.SetDNS() + assert.Nil(t, err) + info, err := os.Stat("./config/dnsconfig/netmaker.hosts") + assert.Nil(t, err) + assert.False(t, info.IsDir()) + content, err := ioutil.ReadFile("./config/dnsconfig/netmaker.hosts") + assert.Nil(t, err) + assert.Contains(t, string(content), "testnode.skynet") + }) + t.Run("EntryExists", func(t *testing.T) { + entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"} + CreateDNS(entry) + err := dnslogic.SetDNS() + assert.Nil(t, err) + info, err := os.Stat("./config/dnsconfig/netmaker.hosts") + assert.Nil(t, err) + assert.False(t, info.IsDir()) + content, err := ioutil.ReadFile("./config/dnsconfig/netmaker.hosts") + assert.Nil(t, err) + assert.Contains(t, string(content), "newhost.skynet") + }) + +} + func TestGetDNSEntry(t *testing.T) { database.InitializeDatabase() + deleteAllDNS(t) deleteAllNetworks() createNet() createTestNode() entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"} CreateDNS(entry) - entry, err := GetDNSEntry("newhost", "skynet") - assert.Nil(t, err) - t.Log(entry) + t.Run("wrong net", func(t *testing.T) { + entry, err := GetDNSEntry("newhost", "w286 Toronto Street South, Uxbridge, ONirecat") + assert.EqualError(t, err, "no result found") + assert.Equal(t, models.DNSEntry{}, entry) + }) + t.Run("wrong host", func(t *testing.T) { + entry, err := GetDNSEntry("badhost", "skynet") + assert.EqualError(t, err, "no result found") + assert.Equal(t, models.DNSEntry{}, entry) + }) + t.Run("good host", func(t *testing.T) { + entry, err := GetDNSEntry("newhost", "skynet") + assert.Nil(t, err) + assert.Equal(t, "newhost", entry.Name) + }) + t.Run("node", func(t *testing.T) { + entry, err := GetDNSEntry("testnode", "skynet") + assert.EqualError(t, err, "no result found") + assert.Equal(t, models.DNSEntry{}, entry) + }) } func TestUpdateDNS(t *testing.T) { + var newentry models.DNSEntry database.InitializeDatabase() + deleteAllDNS(t) + deleteAllNetworks() + createNet() + entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"} + CreateDNS(entry) + t.Run("change address", func(t *testing.T) { + newentry.Address = "10.0.0.75" + updated, err := UpdateDNS(newentry, entry) + assert.Nil(t, err) + assert.Equal(t, newentry.Address, updated.Address) + }) + t.Run("change name", func(t *testing.T) { + newentry.Name = "newname" + updated, err := UpdateDNS(newentry, entry) + assert.Nil(t, err) + assert.Equal(t, newentry.Name, updated.Name) + }) + t.Run("change network", func(t *testing.T) { + newentry.Network = "wirecat" + updated, err := UpdateDNS(newentry, entry) + assert.Nil(t, err) + assert.NotEqual(t, newentry.Network, updated.Network) + }) } func TestDeleteDNS(t *testing.T) { database.InitializeDatabase() + deleteAllDNS(t) + deleteAllNetworks() + createNet() + entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"} + CreateDNS(entry) t.Run("EntryExists", func(t *testing.T) { - err := DeleteDNS("myhost", "skynet") + err := DeleteDNS("newhost", "skynet") assert.Nil(t, err) }) - t.Run("NoEntry", func(t *testing.T) { + t.Run("NodeExists", func(t *testing.T) { err := DeleteDNS("myhost", "skynet") assert.Nil(t, err) }) + t.Run("NoEntries", func(t *testing.T) { + err := DeleteDNS("myhost", "skynet") + assert.Nil(t, err) + }) } func TestValidateDNSUpdate(t *testing.T) { database.InitializeDatabase() + deleteAllDNS(t) + deleteAllNetworks() + createNet() entry := models.DNSEntry{"10.0.0.2", "myhost", "skynet"} - _ = DeleteDNS("mynode", "skynet") t.Run("BadNetwork", func(t *testing.T) { change := models.DNSEntry{"10.0.0.2", "myhost", "badnet"} err := ValidateDNSUpdate(change, entry) @@ -140,11 +349,14 @@ func TestValidateDNSUpdate(t *testing.T) { }) t.Run("NameUnique", func(t *testing.T) { change := models.DNSEntry{"10.0.0.2", "myhost", "wirecat"} - _, _ = CreateDNS(entry) - _, _ = CreateDNS(change) + CreateDNS(entry) + CreateDNS(change) err := ValidateDNSUpdate(change, entry) assert.NotNil(t, err) assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'name_unique' tag") + //cleanup + err = DeleteDNS("myhost", "wirecat") + assert.Nil(t, err) }) } @@ -196,11 +408,9 @@ func TestValidateDNSCreate(t *testing.T) { func deleteAllDNS(t *testing.T) { dns, err := GetAllDNS() - t.Log(err) - t.Log(dns) + assert.Nil(t, err) for _, record := range dns { - t.Log(dns) err := DeleteDNS(record.Name, record.Network) - t.Log(err) + assert.Nil(t, err) } } diff --git a/controllers/userHttpController.go b/controllers/userHttpController.go index 46de2334..999fa89b 100644 --- a/controllers/userHttpController.go +++ b/controllers/userHttpController.go @@ -348,8 +348,8 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { var admin models.User // get node from body of request _ = json.NewDecoder(r.Body).Decode(&admin) - admin.IsAdmin = true - admin, err := CreateUser(admin) + + admin, err := CreateAdmin(admin) if err != nil { returnErrorResponse(w, r, formatError(err, "badrequest")) @@ -359,6 +359,18 @@ func createAdmin(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(admin) } +func CreateAdmin(admin models.User) (models.User, error) { + hasadmin, err := HasAdmin() + if err != nil { + return models.User{}, err + } + if hasadmin { + return models.User{}, errors.New("admin user already exists") + } + admin.IsAdmin = true + return CreateUser(admin) +} + func createUser(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") diff --git a/controllers/userHttpController_test.go b/controllers/userHttpController_test.go index 4cacdf1a..88051c9c 100644 --- a/controllers/userHttpController_test.go +++ b/controllers/userHttpController_test.go @@ -71,6 +71,26 @@ func TestCreateUser(t *testing.T) { }) } +func TestCreateAdmin(t *testing.T) { + database.InitializeDatabase() + deleteAllUsers() + var user models.User + t.Run("NoAdmin", func(t *testing.T) { + user.UserName = "admin" + user.Password = "password" + admin, err := CreateAdmin(user) + assert.Nil(t, err) + assert.Equal(t, user.UserName, admin.UserName) + }) + t.Run("AdminExists", func(t *testing.T) { + user.UserName = "admin2" + user.Password = "password1" + admin, err := CreateAdmin(user) + assert.EqualError(t, err, "admin user already exists") + assert.Equal(t, admin, models.User{}) + }) +} + func TestDeleteUser(t *testing.T) { database.InitializeDatabase() deleteAllUsers() @@ -153,6 +173,49 @@ func TestGetUser(t *testing.T) { }) } +func TestGetUserInternal(t *testing.T) { + database.InitializeDatabase() + deleteAllUsers() + t.Run("NonExistantUser", func(t *testing.T) { + admin, err := GetUserInternal("admin") + assert.EqualError(t, err, "could not find any records") + assert.Equal(t, "", admin.UserName) + }) + t.Run("UserExisits", func(t *testing.T) { + user := models.User{"admin", "password", nil, true} + CreateUser(user) + admin, err := GetUserInternal("admin") + assert.Nil(t, err) + assert.Equal(t, user.UserName, admin.UserName) + }) +} + +func TestGetUsers(t *testing.T) { + database.InitializeDatabase() + deleteAllUsers() + t.Run("NonExistantUser", func(t *testing.T) { + admin, err := GetUsers() + assert.EqualError(t, err, "could not find any records") + assert.Equal(t, []models.ReturnUser(nil), admin) + }) + t.Run("UserExisits", func(t *testing.T) { + user := models.User{"admin", "password", nil, true} + CreateUser(user) + admins, err := GetUsers() + assert.Nil(t, err) + assert.Equal(t, user.UserName, admins[0].UserName) + }) + t.Run("MulipleUsers", func(t *testing.T) { + user := models.User{"user", "password", nil, true} + CreateUser(user) + admins, err := GetUsers() + assert.Nil(t, err) + assert.Equal(t, "admin", admins[0].UserName) + assert.Equal(t, user.UserName, admins[1].UserName) + }) + +} + func TestUpdateUser(t *testing.T) { database.InitializeDatabase() deleteAllUsers() diff --git a/go.mod b/go.mod index fb7785c9..00453777 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 - github.com/lib/pq v1.10.3 // indirect + github.com/lib/pq v1.10.3 github.com/mattn/go-sqlite3 v1.14.8 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e diff --git a/go.sum b/go.sum index 386280bb..2041583c 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,12 @@ +cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -16,7 +20,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad h1:EmNYJhPYy0pOFjCx2PrgtaBXmee0iUX9hLlxE1xHOJE= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -31,6 +37,7 @@ github.com/go-playground/validator/v10 v10.5.0 h1:X9rflw/KmpACwT8zdrm1upefpvdy6u github.com/go-playground/validator/v10 v10.5.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -54,12 +61,14 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA= github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= @@ -112,10 +121,13 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -156,6 +168,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -187,6 +200,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -198,6 +212,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= @@ -208,6 +223,7 @@ golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfM golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210913210325-91d1988e44de h1:M9Jc92kgqmVmidpnOeegP2VgO2DfHEcsUWtWMmBwNFQ= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210913210325-91d1988e44de/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= @@ -236,6 +252,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/kube/helm/README.md b/kube/helm/README.md deleted file mode 100644 index 17168af4..00000000 --- a/kube/helm/README.md +++ /dev/null @@ -1,130 +0,0 @@ -# Netmaker Helm - -![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.9.0](https://img.shields.io/badge/AppVersion-0.9.0-informational?style=flat-square) - -A Helm chart to run Netmaker with High Availability on Kubernetes: - -``` -helm repo add netmaker https://gravitl.github.io/netmaker-helm/ -helm repo update -``` - -## Requirements - -To run HA Netmaker on Kubernetes, your cluster must have the following: -- RWO and RWX Storage Classes (RWX is only required if running Netmaker with DNS Management enabled). -- An Ingress Controller and valid TLS certificates - - This chart can currently generate ingress for: - - Nginx Ingress + LetsEncrypt/Cert-Manager - - Traefik Ingress + LetsEncrypt/Cert-Manager - - to generate automatically, make sure one of the two is configured for your cluster - -Furthermore, the chart will by default install and use a postgresql cluster as its datastore: - -| Repository | Name | Version | -|------------|------|---------| -| https://charts.bitnami.com/bitnami | postgresql-ha | 7.11.0 | - -### Example Install - -``` -helm install netmaker/netmaker --generate-name \ # generate a random id for the deploy ---set baseDomain=nm.example.com \ # the base wildcard domain to use for the netmaker api/dashboard/grpc ingress ---set replicas=3 \ # number of server replicas to deploy (3 by default) ---set ingress.enabled=true \ # deploy ingress automatically (requires nginx or traefik and cert-manager + letsencrypt) ---set ingress.className=nginx \ # ingress class to use ---set ingress.tls.issuerName=letsencrypt-prod \ # LetsEncrypt certificate issuer to use ---set dns.enabled=true \ # deploy and enable private DNS management with CoreDNS ---set dns.clusterIP=10.245.75.75 --set dns.RWX.storageClassName=nfs \ # required fields for DNS ---set postgresql-ha.postgresql.replicaCount=2 \ # number of DB replicas to deploy (default 2) -``` - -### Recommended Settings: -A minimal HA install of Netmaker can be run with the following command: -`helm install netmaker --generate-name --set baseDomain=nm.example.com` -This install has some notable exceptions: -- Ingress **must** be manually configured post-install (need to create valid Ingress with TLS) -- Server will use "userspace" WireGuard, which is slower than kernel WG -- DNS will be disabled - -Below, we discuss the considerations for Ingress, Kernel WireGuard, and DNS. - -#### Ingress -To run HA Netmaker, you must have ingress installed and enabled on your cluster with valid TLS certificates (not self-signed). If you are running Nginx or Traefik as your Ingress Controller and LetsEncrypt for TLS certificate management, you can run the helm install with the following settings: -`--set ingress.enabled=true` -`--set ingress.className=` -`--set ingress.annotations.cert-manager.io/cluster-issuer=` - -If you are not using Nginx or Traefik and LetsEncrypt, we recommend leaving ingress.enabled=false (default), and then manually creating the ingress objects post-install. You will need three ingress objects with TLS: -`dashboard.` -`api.` -`grpc.` - -The gRPC ingress object must include annotations to use the gRPC protocol, which is supported by most ingress controllers. For instance, on Traefik, the annotation is: -`ingress.kubernetes.io/protocol: h2c` - -You can find example ingress objects in the kube/example folder. - -#### Kernel WireGuard -If you have control of the Kubernetes worker node servers, we recommend **first** installing WireGuard on the hosts, and then installing HA Netmaker in Kernel mode. By default, Netmaker will install with userspace WireGuard (wireguard-go) for maximum compatibility, and to avoid needing permissions at the host level. If you have installed WireGuard on your hosts, you should install Netmaker's helm chart with the following option: -`--set wireguard.kernel=true` - -#### DNS -By Default, the helm chart will deploy without DNS enabled. To enable DNS, specify with: -`--set dns.enabled=true` -This will require specifying a RWX storage class, e.g.: -`--set dns.RWX.storageClassName=nfs` -This will also require specifying a service address for DNS. Choose a valid ipv4 address from the service IP CIDR for your cluster, e.g.: -`--set dns.clusterIP=10.245.69.69` - -**This address will only be reachable from hosts that have access to the cluster service CIDR.** It is only designed for use cases related to k8s. If you want a more general-use Netmaker server on Kubernetes for use cases outside of k8s, you will need to do one of the following: -- bind the CoreDNS service to port 53 on one of your worker nodes and set the COREDNS_ADDRESS equal to the public IP of the worker node -- Create a private Network with Netmaker and set the COREDNS_ADDRESS equal to the private address of the host running CoreDNS. For this, CoreDNS will need a node selector and will ideally run on the same host as one of the Netmaker server instances. - - - -## Values - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| dns.enabled | bool | `false` | whether or not to run with DNS (CoreDNS) | -| dns.storageSize | string | `"128Mi"` | volume size for DNS (only needs to hold one file) | -| fullnameOverride | string | `""` | override the full name for netmaker objects | -| image.pullPolicy | string | `"Always"` | Pull Policy for images | -| image.repository | string | `"gravitl/netmaker"` | The image repo to pull Netmaker image from | -| image.tag | string | `"v0.8.4"` | Override the image tag to pull | -| ingress.annotations.base."kubernetes.io/ingress.allow-http" | string | `"false"` | annotation to generate ACME certs if available | -| ingress.annotations.grpc.nginx."nginx.ingress.kubernetes.io/backend-protocol" | string | `"GRPC"` | annotation to use grpc protocol on grpc domain | -| ingress.annotations.grpc.traefik."ingress.kubernetes.io/protocol" | string | `"h2c"` | annotation to use grpc protocol on grpc domain | -| ingress.annotations.nginx."nginx.ingress.kubernetes.io/rewrite-target" | string | `"/"` | destination addr for route | -| ingress.annotations.nginx."nginx.ingress.kubernetes.io/ssl-redirect" | string | `"true"` | Redirect http to https | -| ingress.annotations.tls."kubernetes.io/tls-acme" | string | `"true"` | use acme cert if available | -| ingress.annotations.traefik."traefik.ingress.kubernetes.io/redirect-entry-point" | string | `"https"` | Redirect to https | -| ingress.annotations.traefik."traefik.ingress.kubernetes.io/redirect-permanent" | string | `"true"` | Redirect to https permanently | -| ingress.annotations.traefik."traefik.ingress.kubernetes.io/rule-type" | string | `"PathPrefixStrip"` | rule type | -| ingress.enabled | bool | `false` | attempts to configure ingress if true | -| ingress.hostPrefix.grpc | string | `"grpc."` | grpc route subdomain | -| ingress.hostPrefix.rest | string | `"api."` | api (REST) route subdomain | -| ingress.hostPrefix.ui | string | `"dashboard."` | ui route subdomain | -| ingress.tls.enabled | bool | `true` | | -| ingress.tls.issuerName | string | `"letsencrypt-prod"` | | -| nameOverride | string | `""` | override the name for netmaker objects | -| podAnnotations | object | `{}` | pod annotations to add | -| podSecurityContext | object | `{}` | pod security contect to add | -| postgresql-ha.persistence.size | string | `"3Gi"` | size of postgres DB | -| postgresql-ha.postgresql.database | string | `"netmaker"` | postgress db to generate | -| postgresql-ha.postgresql.password | string | `"netmaker"` | postgres pass to generate | -| postgresql-ha.postgresql.username | string | `"netmaker"` | postgres user to generate | -| replicas | int | `3` | number of netmaker server replicas to create | -| service.grpcPort | int | `443` | port for GRPC service | -| service.restPort | int | `8081` | port for API service | -| service.type | string | `"ClusterIP"` | type for netmaker server services | -| service.uiPort | int | `80` | port for UI service | -| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | -| serviceAccount.create | bool | `true` | Specifies whether a service account should be created | -| serviceAccount.name | string | `""` | Name of SA to use. If not set and create is true, a name is generated using the fullname template | -| ui.replicas | int | `2` | how many UI replicas to create | -| wireguard.enabled | bool | `true` | whether or not to use WireGuard on server | -| wireguard.kernel | bool | `false` | whether or not to use Kernel WG (should be false unless WireGuard is installed on hosts). | -| wireguard.networkLimit | int | `10` | max number of networks that Netmaker will support if running with WireGuard enabled | - diff --git a/kube/helm/netmaker/Chart.lock b/kube/helm/netmaker/Chart.lock deleted file mode 100644 index 035a2f3b..00000000 --- a/kube/helm/netmaker/Chart.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: postgresql-ha - repository: https://charts.bitnami.com/bitnami - version: 7.11.0 -digest: sha256:849759b9fd9d89bf0d47a271334889601010d1d11dd5c00562c18feafd93356d -generated: "2021-10-13T14:02:45.428151972-04:00" diff --git a/kube/helm/netmaker/Chart.yaml b/kube/helm/netmaker/Chart.yaml deleted file mode 100644 index e036b100..00000000 --- a/kube/helm/netmaker/Chart.yaml +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: v2 -name: netmaker -description: A Helm chart to run HA Netmaker on Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "0.9.0" - -dependencies: - - name: "postgresql-ha" - version: "7.11.0" - repository: https://charts.bitnami.com/bitnami diff --git a/kube/helm/netmaker/README.md b/kube/helm/netmaker/README.md deleted file mode 100644 index a8f5a9f3..00000000 --- a/kube/helm/netmaker/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# netmaker - -![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.9.0](https://img.shields.io/badge/AppVersion-0.9.0-informational?style=flat-square) - -A Helm chart to run HA Netmaker on Kubernetes - -## Requirements - -| Repository | Name | Version | -|------------|------|---------| -| https://charts.bitnami.com/bitnami | postgresql-ha | 7.11.0 | - -## Values - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| dns.enabled | bool | `false` | whether or not to run with DNS (CoreDNS) | -| dns.storageSize | string | `"128Mi"` | volume size for DNS (only needs to hold one file) | -| fullnameOverride | string | `""` | override the full name for netmaker objects | -| image.pullPolicy | string | `"Always"` | Pull Policy for images | -| image.repository | string | `"gravitl/netmaker"` | The image repo to pull Netmaker image from | -| image.tag | string | `"v0.8.4"` | Override the image tag to pull | -| ingress.annotations.base."kubernetes.io/ingress.allow-http" | string | `"false"` | annotation to generate ACME certs if available | -| ingress.annotations.grpc.nginx."nginx.ingress.kubernetes.io/backend-protocol" | string | `"GRPC"` | annotation to use grpc protocol on grpc domain | -| ingress.annotations.grpc.traefik."ingress.kubernetes.io/protocol" | string | `"h2c"` | annotation to use grpc protocol on grpc domain | -| ingress.annotations.nginx."nginx.ingress.kubernetes.io/rewrite-target" | string | `"/"` | destination addr for route | -| ingress.annotations.nginx."nginx.ingress.kubernetes.io/ssl-redirect" | string | `"true"` | Redirect http to https | -| ingress.annotations.tls."kubernetes.io/tls-acme" | string | `"true"` | use acme cert if available | -| ingress.annotations.traefik."traefik.ingress.kubernetes.io/redirect-entry-point" | string | `"https"` | Redirect to https | -| ingress.annotations.traefik."traefik.ingress.kubernetes.io/redirect-permanent" | string | `"true"` | Redirect to https permanently | -| ingress.annotations.traefik."traefik.ingress.kubernetes.io/rule-type" | string | `"PathPrefixStrip"` | rule type | -| ingress.enabled | bool | `false` | attempts to configure ingress if true | -| ingress.hostPrefix.grpc | string | `"grpc."` | grpc route subdomain | -| ingress.hostPrefix.rest | string | `"api."` | api (REST) route subdomain | -| ingress.hostPrefix.ui | string | `"dashboard."` | ui route subdomain | -| ingress.tls.enabled | bool | `true` | | -| ingress.tls.issuerName | string | `"letsencrypt-prod"` | | -| nameOverride | string | `""` | override the name for netmaker objects | -| podAnnotations | object | `{}` | pod annotations to add | -| podSecurityContext | object | `{}` | pod security contect to add | -| postgresql-ha.persistence.size | string | `"3Gi"` | size of postgres DB | -| postgresql-ha.postgresql.database | string | `"netmaker"` | postgress db to generate | -| postgresql-ha.postgresql.password | string | `"netmaker"` | postgres pass to generate | -| postgresql-ha.postgresql.username | string | `"netmaker"` | postgres user to generate | -| replicas | int | `3` | number of netmaker server replicas to create | -| service.grpcPort | int | `443` | port for GRPC service | -| service.restPort | int | `8081` | port for API service | -| service.type | string | `"ClusterIP"` | type for netmaker server services | -| service.uiPort | int | `80` | port for UI service | -| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | -| serviceAccount.create | bool | `true` | Specifies whether a service account should be created | -| serviceAccount.name | string | `""` | Name of SA to use. If not set and create is true, a name is generated using the fullname template | -| ui.replicas | int | `2` | how many UI replicas to create | -| wireguard.enabled | bool | `true` | whether or not to use WireGuard on server | -| wireguard.kernel | bool | `false` | whether or not to use Kernel WG (should be false unless WireGuard is installed on hosts). | -| wireguard.networkLimit | int | `10` | max number of networks that Netmaker will support if running with WireGuard enabled | - diff --git a/kube/helm/netmaker/charts/postgresql-ha-7.11.0.tgz b/kube/helm/netmaker/charts/postgresql-ha-7.11.0.tgz deleted file mode 100644 index 6c79ddd50c41df1c04a80a2d730d8b6b29630c17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53745 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 diff --git a/kube/helm/netmaker/templates/NOTES.txt b/kube/helm/netmaker/templates/NOTES.txt deleted file mode 100644 index 53b369ed..00000000 --- a/kube/helm/netmaker/templates/NOTES.txt +++ /dev/null @@ -1,22 +0,0 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.enabled }} -{{- range $host := .Values.ingress.hosts }} - {{- range .paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "netmaker.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "netmaker.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "netmaker.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "netmaker.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT -{{- end }} diff --git a/kube/helm/netmaker/templates/_helpers.tpl b/kube/helm/netmaker/templates/_helpers.tpl deleted file mode 100644 index 4e815fb2..00000000 --- a/kube/helm/netmaker/templates/_helpers.tpl +++ /dev/null @@ -1,70 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "netmaker.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "netmaker.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "netmaker.masterKey" -}} -{{- randAlphaNum 12 | nospace -}} -{{- end -}} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "netmaker.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "netmaker.labels" -}} -helm.sh/chart: {{ include "netmaker.chart" . }} -{{ include "netmaker.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "netmaker.selectorLabels" -}} -app.kubernetes.io/name: {{ include "netmaker.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "netmaker.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "netmaker.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/kube/helm/netmaker/templates/coredns.yaml b/kube/helm/netmaker/templates/coredns.yaml deleted file mode 100644 index 8102ac82..00000000 --- a/kube/helm/netmaker/templates/coredns.yaml +++ /dev/null @@ -1,85 +0,0 @@ -{{- if .Values.dns.enabled -}} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "netmaker.fullname" . }}-coredns - labels: - app: {{ include "netmaker.fullname" . }}-coredns -spec: - selector: - matchLabels: - app: {{ include "netmaker.fullname" . }}-coredns - replicas: 1 - template: - metadata: - labels: - app: {{ include "netmaker.fullname" . }}-coredns - spec: - containers: - - args: - - -conf - - /root/dnsconfig/Corefile - image: coredns/coredns - imagePullPolicy: Always - name: netmaker-dns - ports: - - containerPort: 53 - name: dns - protocol: UDP - - containerPort: 53 - name: dns-tcp - protocol: TCP - volumeMounts: - - mountPath: /root/dnsconfig - name: {{ include "netmaker.fullname" . }}-dns-pvc - readOnly: true - securityContext: - allowPrivilegeEscalation: false - capabilities: - add: - - NET_BIND_SERVICE - drop: - - all - dnsPolicy: "None" - dnsConfig: - nameservers: - - 127.0.0.1 - volumes: - - name: {{ include "netmaker.fullname" . }}-dns-pvc - persistentVolumeClaim: - claimName: {{ include "netmaker.fullname" . }}-dns-pvc ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app: {{ include "netmaker.fullname" . }}-coredns - name: {{ include "netmaker.fullname" . }}-coredns -spec: - ports: - - port: 53 - protocol: UDP - targetPort: 53 - name: udp - - port: 53 - protocol: TCP - targetPort: 53 - name: tcp - selector: - app: {{ include "netmaker.fullname" . }}-coredns - sessionAffinity: None - type: ClusterIP - clusterIP: {{ required "A valid .Values.dns.clusterIP entry required! Choose an IP from your k8s service IP CIDR" .Values.dns.clusterIP}} ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ include "netmaker.fullname" . }}-dns-pvc -spec: - storageClassName: {{ required "A valid .Values.dns.RWX.storageClassName entry required! Specify an available RWX storage class." .Values.dns.RWX.storageClassName}} - accessModes: - - ReadWriteMany - resources: - requests: - storage: {{ .Values.dns.storageSize }} -{{- end }} \ No newline at end of file diff --git a/kube/helm/netmaker/templates/ingress.yaml b/kube/helm/netmaker/templates/ingress.yaml deleted file mode 100644 index c26df0f7..00000000 --- a/kube/helm/netmaker/templates/ingress.yaml +++ /dev/null @@ -1,236 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "netmaker.fullname" . -}} -{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} -{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} -{{- $fullGRPCName := printf "%s-%s" $fullName "grpc" -}} -{{- $uiSvcPort := .Values.service.uiPort -}} -{{- $restSvcPort := .Values.service.restPort -}} -{{- $grpcSvcPort := .Values.service.grpcPort -}} -{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullUIName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} - {{- with .Values.ingress }} - annotations: - {{- toYaml .annotations.base | nindent 4 }} - {{- if or (eq .className "nginx") (eq .className "public") }} - {{- toYaml .annotations.nginx | nindent 4 }} - {{- end }} - {{- if eq .className "traefik" }} - {{- toYaml .annotations.traefik | nindent 4 }} - {{- end }} - {{- if and .tls.enabled (eq .tls.issuerName "" )}} - {{- toYaml .annotations.tls | nindent 4 }} - {{- else if .tls.enabled}} - cert-manager.io/cluster-issuer: {{ .tls.issuerName }} - {{- end }} - {{- end }} -spec: - {{- if (not (eq .Values.ingress.className "traefik")) }} - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} - {{- end }} - {{- end }} - {{- if .Values.ingress.tls.enabled }} - tls: - - hosts: - - {{ .Values.ingress.hostPrefix.ui }}{{ .Values.baseDomain }} - secretName: {{ $fullUIName }}-tls-secret - {{- end}} - rules: - - host: {{ .Values.ingress.hostPrefix.ui }}{{ .Values.baseDomain }} - http: - paths: - - path: / - {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: Prefix - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullUIName }} - port: - number: {{ $uiSvcPort }} - {{- else }} - serviceName: {{ $fullUIName }} - servicePort: {{ $uiSvcPort }} - {{- end }} ---- -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullRESTName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} - {{- with .Values.ingress }} - annotations: - {{- toYaml .annotations.base | nindent 4 }} - {{- if or (eq .className "nginx") (eq .className "public") }} - {{- toYaml .annotations.nginx | nindent 4 }} - {{- end }} - {{- if eq .className "traefik" }} - {{- toYaml .annotations.traefik | nindent 4 }} - {{- end }} - {{- if and .tls.enabled (eq .tls.issuerName "" )}} - {{- toYaml .annotations.tls | nindent 4 }} - {{- else if .tls.enabled}} - cert-manager.io/cluster-issuer: {{ .tls.issuerName }} - {{- end }} - {{- end }} -spec: - {{- if (not (eq .Values.ingress.className "traefik")) }} - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} - {{- end }} - {{- end }} - {{- if .Values.ingress.tls.enabled }} - tls: - - hosts: - - {{ .Values.ingress.hostPrefix.rest }}{{ .Values.baseDomain }} - secretName: {{ $fullRESTName }}-tls-secret - {{- end }} - rules: - - host: {{ .Values.ingress.hostPrefix.rest }}{{ .Values.baseDomain }} - http: - paths: - - path: / - {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: Prefix - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullRESTName }} - port: - number: {{ $restSvcPort }} - {{- else }} - serviceName: {{ $fullRESTName }} - servicePort: {{ $restSvcPort }} - {{- end }} ---- -{{- if not (eq .Values.ingress.className "traefik") }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullGRPCName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} - {{- with .Values.ingress }} - annotations: - {{- toYaml .annotations.base | nindent 4 }} - {{- if or (eq .className "nginx") (eq .className "public") }} - {{- toYaml .annotations.nginx | nindent 4 }} - {{- toYaml .annotations.grpc.nginx | nindent 4 }} - {{- end }} - {{- if eq .className "traefik" }} - {{- toYaml .annotations.traefik | nindent 4 }} - {{- end }} - {{- if and .tls.enabled (eq .tls.issuerName "" )}} - {{- toYaml .annotations.tls | nindent 4 }} - {{- else if .tls.enabled}} - cert-manager.io/cluster-issuer: {{ .tls.issuerName }} - {{- end }} - {{- end }} -spec: - {{- if (not (eq .Values.ingress.className "traefik")) }} - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} - {{- end }} - {{- end }} - {{- if .Values.ingress.tls.enabled }} - tls: - - hosts: - - {{ .Values.ingress.hostPrefix.grpc }}{{ .Values.baseDomain }} - secretName: {{ $fullGRPCName }}-tls-secret - {{- end }} - rules: - - host: {{ .Values.ingress.hostPrefix.grpc }}{{ .Values.baseDomain }} - http: - paths: - - path: / - {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: Prefix - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullGRPCName }} - port: - number: {{ $grpcSvcPort }} - {{- else }} - serviceName: {{ $fullGRPCName }} - servicePort: {{ $grpcSvcPort }} - {{- end }} -{{- end }} -{{- if eq .Values.ingress.className "traefik" }} ---- -apiVersion: traefik.containo.us/v1alpha1 -kind: IngressRouteTCP -metadata: - name: {{ $fullGRPCName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} -spec: - entryPoints: - - websecure - routes: - - match: HostSNI(`{{ .Values.ingress.hostPrefix.grpc }}{{ .Values.baseDomain }}`) - services: - - name: {{ $fullGRPCName }} - port: {{ $grpcSvcPort }} - passthrough: true - scheme: https - tls: - secretName: {{ $fullGRPCName }}-tls-secret - domains: - - main: {{ .Values.ingress.hostPrefix.grpc }}{{ .Values.baseDomain }} -{{- if and .Values.ingress.tls.enabled (not (eq .Values.ingress.tls.issuerName "" ))}} ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - annotations: - acme.cert-manager.io/http01-override-ingress-name: {{ $fullRESTName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} - name: {{ $fullGRPCName }}-tls-secret -spec: - dnsNames: - - {{ .Values.ingress.hostPrefix.grpc }}{{ .Values.baseDomain }} - issuerRef: - group: cert-manager.io - kind: ClusterIssuer - name: {{ .Values.ingress.tls.issuerName }} - secretName: {{ $fullGRPCName }}-tls-secret - usages: - - digital signature - - key encipherment -{{- end }} -{{- end }} -{{- end }} diff --git a/kube/helm/netmaker/templates/netmaker-statefulset.yaml b/kube/helm/netmaker/templates/netmaker-statefulset.yaml deleted file mode 100644 index 9e538495..00000000 --- a/kube/helm/netmaker/templates/netmaker-statefulset.yaml +++ /dev/null @@ -1,133 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - labels: - app: {{ include "netmaker.fullname" . }} - name: {{ include "netmaker.fullname" . }} -spec: - replicas: {{ .Values.replicas }} - serviceName: {{ include "netmaker.fullname" . }}-headless - selector: - matchLabels: - app: {{ include "netmaker.fullname" . }} - template: - metadata: - labels: - app: {{ include "netmaker.fullname" . }} - spec: - {{- if .Values.wireguard.enabled }} - {{- if .Values.setIpForwarding.enabled }} - initContainers: - - name: init-sysctl - image: busybox - imagePullPolicy: IfNotPresent - command: ["sysctl", "-w", "net.ipv4.ip_forward=1"] - securityContext: - privileged: true - {{- end }} - dnsPolicy: ClusterFirstWithHostNet - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - {{ include "netmaker.fullname" . }} - topologyKey: "kubernetes.io/hostname" - {{- end }} - containers: - - env: - - name: SERVER_API_CONN_STRING - value: api.{{ required "A valid .Values.baseDomain entry required!" .Values.baseDomain}}:443 - - name: SERVER_GRPC_CONN_STRING - value: grpc.{{ required "A valid .Values.baseDomain entry required!" .Values.baseDomain}}:443 - - name: GRPC_SSL - value: "on" - - name: SERVER_HTTP_HOST - value: api.{{ required "A valid .Values.baseDomain entry required!" .Values.baseDomain}} - - name: SERVER_GRPC_HOST - value: grpc.{{ required "A valid .Values.baseDomain entry required!" .Values.baseDomain}} - - name: API_PORT - value: "8081" - {{- if not .Values.wireguard.kernel }} - - name: WG_QUICK_USERSPACE_IMPLEMENTATION - value: wireguard-go - {{- end }} - - name: GRPC_PORT - value: "443" - {{- if .Values.dns.enabled }} - - name: DNS_MODE - value: "on" - - name: COREDNS_ADDR - value: {{ required "A valid .Values.dns.clusterIP entry required! Choose an IP from your k8s service IP CIDR" .Values.dns.clusterIP }} - {{- else }} - - name: DNS_MODE - value: "off" - {{- end }} - {{- if .Values.wireguard.enabled }} - - name: CLIENT_MODE - value: "on" - {{- else }} - - name: CLIENT_MODE - value: "off" - {{- end }} - - name: MASTER_KEY - value: {{ include "netmaker.masterKey" . }} - - name: PLATFORM - value: Kubernetes - - name: CORS_ALLOWED_ORIGIN - value: '*' - - name: NODE_ID - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.name - - name: SQL_HOST - value: '{{ .Release.Name }}-postgresql-ha-pgpool.{{ .Release.Namespace }}.svc.cluster.local' - - name: SQL_PORT - value: "5432" - - name: SQL_DB - value: {{ index .Values "postgresql-ha" "postgresql" "database" }} - - name: SQL_USER - value: {{ index .Values "postgresql-ha" "postgresql" "username" }} - - name: SQL_PASS - value: {{ index .Values "postgresql-ha" "postgresql" "password" }} - - name: DATABASE - value: postgres - {{- if or (not .Values.wireguard.enabled) (.Values.wireguard.kernel) }} - image: gravitl/netmaker:v0.8.4 - {{- else }} - image: gravitl/netmaker:v0.8.4-userspace - {{- end }} - imagePullPolicy: Always - name: {{ include "netmaker.fullname" . }} - ports: - - containerPort: {{ .Values.service.restPort }} - protocol: TCP - - containerPort: {{ .Values.service.grpcPort }} - protocol: TCP - {{- if .Values.wireguard.enabled }} - {{ $count := (add .Values.wireguard.networkLimit 1 | int) }} - {{- range untilStep 1 $count 1 }} - - containerPort: {{ add 31820 . }} - protocol: UDP - {{- end }} - {{- end }} - resources: {} - {{- if .Values.wireguard.enabled }} - securityContext: - capabilities: - add: - - NET_ADMIN - {{- end }} - {{- if .Values.dns.enabled }} - volumeMounts: - - name: {{ include "netmaker.fullname" . }}-dns-pvc - mountPath: /root/config/dnsconfig - volumes: - - name: {{ include "netmaker.fullname" . }}-dns-pvc - persistentVolumeClaim: - claimName: {{ include "netmaker.fullname" . }}-dns-pvc - {{- end }} \ No newline at end of file diff --git a/kube/helm/netmaker/templates/netmaker-ui-deployment.yaml b/kube/helm/netmaker/templates/netmaker-ui-deployment.yaml deleted file mode 100644 index b105786a..00000000 --- a/kube/helm/netmaker/templates/netmaker-ui-deployment.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: {{ include "netmaker.fullname" . }}-ui - name: {{ include "netmaker.fullname" . }}-ui -spec: - replicas: {{ .Values.ui.replicas }} - selector: - matchLabels: - app: {{ include "netmaker.fullname" . }}-ui - template: - metadata: - labels: - app: {{ include "netmaker.fullname" . }}-ui - spec: - containers: - - name: {{ include "netmaker.fullname" . }}-ui - image: gravitl/netmaker-ui:v0.8 - ports: - - containerPort: {{ .Values.service.grpcPort }} - env: - - name: BACKEND_URL - value: 'https://{{ .Values.ingress.hostPrefix.rest }}{{ required "A valid .Values.baseDomain entry required!" .Values.baseDomain}}' - terminationGracePeriodSeconds: 15 \ No newline at end of file diff --git a/kube/helm/netmaker/templates/serviceaccount.yaml b/kube/helm/netmaker/templates/serviceaccount.yaml deleted file mode 100644 index f44de451..00000000 --- a/kube/helm/netmaker/templates/serviceaccount.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "netmaker.serviceAccountName" . }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} diff --git a/kube/helm/netmaker/templates/services.yaml b/kube/helm/netmaker/templates/services.yaml deleted file mode 100644 index 8f5bfbbb..00000000 --- a/kube/helm/netmaker/templates/services.yaml +++ /dev/null @@ -1,72 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - {{- include "netmaker.labels" . | nindent 4 }} - name: '{{ include "netmaker.fullname" . }}-ui' -spec: - ports: - - port: {{ .Values.service.uiPort }} - protocol: TCP - targetPort: {{ .Values.service.uiPort }} - selector: - app: '{{ include "netmaker.fullname" . }}-ui' - sessionAffinity: None - type: '{{ .Values.service.type }}' ---- -apiVersion: v1 -kind: Service -metadata: - labels: - {{- include "netmaker.labels" . | nindent 4 }} - name: '{{ include "netmaker.fullname" . }}-rest' -spec: - ports: - - name: rest - port: {{ .Values.service.restPort }} - protocol: TCP - targetPort: {{ .Values.service.restPort }} - selector: - app: '{{ include "netmaker.fullname" . }}' - sessionAffinity: None - type: {{ .Values.service.type }} ---- -apiVersion: v1 -kind: Service -metadata: - labels: - {{- include "netmaker.labels" . | nindent 4 }} - name: '{{ include "netmaker.fullname" . }}-grpc' -spec: - ports: - - name: rest - port: {{ .Values.service.grpcPort }} - protocol: TCP - targetPort: {{ .Values.service.grpcPort }} - selector: - app: '{{ include "netmaker.fullname" . }}' - sessionAffinity: None - type: {{ .Values.service.type }} -{{- if .Values.wireguard.enabled }} ---- -apiVersion: v1 -kind: Service -metadata: - labels: - {{- include "netmaker.labels" . | nindent 4 }} - name: '{{ include "netmaker.fullname" . }}-wireguard' -spec: - externalTrafficPolicy: Local - type: NodePort - ports: - {{ $count := (add .Values.wireguard.networkLimit 1 | int) }} - {{- range untilStep 1 $count 1 }} - - port: {{ add 31820 . }} - nodePort: {{ add 31820 . }} - protocol: UDP - targetPort: {{ add 31820 . }} - name: wg-iface-{{ add 31820 . }} - {{- end }} - selector: - app: '{{ include "netmaker.fullname" . }}' -{{- end }} \ No newline at end of file diff --git a/kube/helm/netmaker/templates/tests/test-connection.yaml b/kube/helm/netmaker/templates/tests/test-connection.yaml deleted file mode 100644 index c0d498cc..00000000 --- a/kube/helm/netmaker/templates/tests/test-connection.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ include "netmaker.fullname" . }}-test-connection" - labels: - {{- include "netmaker.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test -spec: - containers: - - name: wget - image: busybox - command: ['wget'] - args: ['{{ include "netmaker.fullname" . }}:{{ .Values.service.port }}'] - restartPolicy: Never diff --git a/kube/helm/netmaker/values.yaml b/kube/helm/netmaker/values.yaml deleted file mode 100644 index d89c3167..00000000 --- a/kube/helm/netmaker/values.yaml +++ /dev/null @@ -1,124 +0,0 @@ -# Default values for netmaker. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -# -- number of netmaker server replicas to create -replicas: 3 - -image: - # -- The image repo to pull Netmaker image from - repository: gravitl/netmaker - # -- Pull Policy for images - pullPolicy: Always - # -- Override the image tag to pull - tag: "v0.8.4" - -# -- override the name for netmaker objects -nameOverride: "" - -# -- override the full name for netmaker objects -fullnameOverride: "" - -serviceAccount: - # -- Specifies whether a service account should be created - create: true - # -- Annotations to add to the service account - annotations: {} - # -- Name of SA to use. If not set and create is true, a name is generated using the fullname template - name: "" - -# -- pod annotations to add -podAnnotations: {} - -# -- pod security contect to add -podSecurityContext: {} - # fsGroup: 2000 - -ui: - # -- how many UI replicas to create - replicas: 2 - -setIpForwarding: - enabled: true - -service: - # -- type for netmaker server services - type: ClusterIP - # -- port for API service - restPort: 8081 - # -- port for GRPC service - grpcPort: 443 - # -- port for UI service - uiPort: 80 - -ingress: - # -- attempts to configure ingress if true - enabled: false - tls: - enabled: true - issuerName: "letsencrypt-prod" - annotations: - base: - # -- annotation to generate ACME certs if available - kubernetes.io/ingress.allow-http: "false" - tls: - # -- use acme cert if available - kubernetes.io/tls-acme: "true" - nginx: - # -- Redirect http to https - nginx.ingress.kubernetes.io/ssl-redirect: 'true' - # -- destination addr for route - nginx.ingress.kubernetes.io/rewrite-target: / - traefik: - # -- Redirect to https - traefik.ingress.kubernetes.io/redirect-entry-point: https - # -- Redirect to https permanently - traefik.ingress.kubernetes.io/redirect-permanent: "true" - # -- rule type - traefik.ingress.kubernetes.io/rule-type: "PathPrefixStrip" - # -- enforce https - traefik.ingress.kubernetes.io/router.entrypoints: websecure - # -- enforce tls - traefik.ingress.kubernetes.io/router.tls: "true" - grpc: - nginx: - # -- annotation to use grpc protocol on grpc domain - nginx.ingress.kubernetes.io/backend-protocol: "GRPC" - traefik: - # -- annotation to use grpc protocol on grpc domain - ingress.kubernetes.io/protocol: "h2c" - hostPrefix: - # -- ui route subdomain - ui: 'dashboard.' - # -- api (REST) route subdomain - rest: 'api.' - # -- grpc route subdomain - grpc: 'grpc.' - -wireguard: - # -- whether or not to use WireGuard on server - enabled: true - # -- whether or not to use Kernel WG (should be false unless WireGuard is installed on hosts). - kernel: false - # -- max number of networks that Netmaker will support if running with WireGuard enabled - networkLimit: 10 - -dns: - # -- whether or not to run with DNS (CoreDNS) - enabled: false - # -- volume size for DNS (only needs to hold one file) - storageSize: 128Mi - -postgresql-ha: - postgresql: - # -- postgres user to generate - username: netmaker - # -- postgres pass to generate - password: netmaker - # -- postgress db to generate - database: netmaker - # -- postgress number of replicas to deploy - replicaCount: 2 - persistence: - # -- size of postgres DB - size: 3Gi diff --git a/logic/util.go b/logic/util.go index a25442ff..fcd6e5d3 100644 --- a/logic/util.go +++ b/logic/util.go @@ -280,6 +280,7 @@ func setPeerInfo(node models.Node) models.Node { peer.IsRelayed = node.IsRelayed peer.PublicKey = node.PublicKey peer.Endpoint = node.Endpoint + peer.Name = node.Name peer.LocalAddress = node.LocalAddress peer.ListenPort = node.ListenPort peer.AllowedIPs = node.AllowedIPs diff --git a/netclient/command/commands.go b/netclient/command/commands.go index a96a1d68..adaa0822 100644 --- a/netclient/command/commands.go +++ b/netclient/command/commands.go @@ -192,7 +192,7 @@ func Pull(cfg config.ClientConfig) error { } func List(cfg config.ClientConfig) error { - err := functions.List() + err := functions.List(cfg.Network) return err } diff --git a/netclient/daemon/systemd.go b/netclient/daemon/systemd.go index 6b90f1f4..914c2f0c 100644 --- a/netclient/daemon/systemd.go +++ b/netclient/daemon/systemd.go @@ -97,6 +97,13 @@ WantedBy=timers.target return nil } +func CleanupLinux() { + err := os.RemoveAll(ncutils.GetNetclientPath()) + if err != nil { + ncutils.PrintLog("Removing netclient binary: "+err.Error(), 1) + } +} + // RemoveSystemDServices - removes the systemd services on a machine func RemoveSystemDServices() error { //sysExec, err := exec.LookPath("systemctl") diff --git a/netclient/functions/common.go b/netclient/functions/common.go index e8725fd3..7982b02b 100644 --- a/netclient/functions/common.go +++ b/netclient/functions/common.go @@ -158,6 +158,8 @@ func Uninstall() error { daemon.CleanupWindows() } else if ncutils.IsMac() { daemon.CleanupMac() + } else if ncutils.IsLinux() { + daemon.CleanupLinux() } else if !ncutils.IsKernel() { ncutils.PrintLog("manual cleanup required", 1) } @@ -255,32 +257,6 @@ func DeleteInterface(ifacename string, postdown string) error { return err } -// List - lists all networks on local machine -func List() error { - - networks, err := ncutils.GetSystemNetworks() - if err != nil { - return err - } - for _, network := range networks { - cfg, err := config.ReadConfig(network) - if err == nil { - jsoncfg, _ := json.Marshal( - map[string]string{ - "Name": cfg.Node.Name, - "Interface": cfg.Node.Interface, - "PrivateIPv4": cfg.Node.Address, - "PrivateIPv6": cfg.Node.Address6, - "PublicEndpoint": cfg.Node.Endpoint, - }) - fmt.Println(network + ": " + string(jsoncfg)) - } else { - ncutils.PrintLog(network+": Could not retrieve network configuration.", 1) - } - } - return nil -} - // WipeLocal - wipes local instance func WipeLocal(network string) error { cfg, err := config.ReadConfig(network) diff --git a/netclient/functions/list.go b/netclient/functions/list.go new file mode 100644 index 00000000..f5a0d92b --- /dev/null +++ b/netclient/functions/list.go @@ -0,0 +1,128 @@ +package functions + +import ( + "encoding/json" + "fmt" + + nodepb "github.com/gravitl/netmaker/grpc" + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/netclient/auth" + "github.com/gravitl/netmaker/netclient/config" + "github.com/gravitl/netmaker/netclient/ncutils" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type Peer struct { + Name string `json:"name"` + Interface string `json:"interface,omitempty"` + PrivateIPv4 string `json:"private_ipv4,omitempty"` + PrivateIPv6 string `json:"private_ipv6,omitempty"` + PublicEndpoint string `json:"public_endoint,omitempty"` +} + +type Network struct { + Name string `json:"name"` + CurrentNode Peer `json:"current_node"` + Peers []Peer `json:"peers"` +} + +func List(network string) error { + nets := []Network{} + var err error + var networks []string + if network == "all" { + networks, err = ncutils.GetSystemNetworks() + if err != nil { + return err + } + } else { + networks = append(networks, network) + } + + for _, network := range networks { + net, err := getNetwork(network) + if err != nil { + ncutils.PrintLog(network+": Could not retrieve network configuration.", 1) + return err + } + nets = append(nets, net) + } + + jsoncfg, _ := json.Marshal(struct { + Networks []Network `json:"networks"` + }{nets}) + fmt.Println(string(jsoncfg)) + + return nil +} + +func getNetwork(network string) (Network, error) { + cfg, err := config.ReadConfig(network) + if err != nil { + return Network{}, fmt.Errorf("reading configuration for network %v: %w", network, err) + } + peers, err := getPeers(network) + if err != nil { + return Network{}, fmt.Errorf("listing peers for network %v: %w", network, err) + } + return Network{ + Name: network, + Peers: peers, + CurrentNode: Peer{ + Name: cfg.Node.Name, + Interface: cfg.Node.Interface, + PrivateIPv4: cfg.Node.Address, + PrivateIPv6: cfg.Node.Address6, + PublicEndpoint: cfg.Node.Endpoint, + }, + }, nil +} + +func getPeers(network string) ([]Peer, error) { + cfg, err := config.ReadConfig(network) + if err != nil { + return []Peer{}, err + } + nodecfg := cfg.Node + var nodes []models.Node + + var wcclient nodepb.NodeServiceClient + conn, err := grpc.Dial(cfg.Server.GRPCAddress, + ncutils.GRPCRequestOpts(cfg.Server.GRPCSSL)) + + if err != nil { + return []Peer{}, fmt.Errorf("connecting to %v: %w", cfg.Server.GRPCAddress, err) + } + defer conn.Close() + // Instantiate the BlogServiceClient with our client connection to the server + wcclient = nodepb.NewNodeServiceClient(conn) + + req := &nodepb.Object{ + Data: nodecfg.MacAddress + "###" + nodecfg.Network, + Type: nodepb.STRING_TYPE, + } + + ctx, err := auth.SetJWT(wcclient, network) + if err != nil { + return []Peer{}, fmt.Errorf("authenticating: %w", err) + } + var header metadata.MD + + response, err := wcclient.GetPeers(ctx, req, grpc.Header(&header)) + if err != nil { + return []Peer{}, fmt.Errorf("retrieving peers: %w", err) + } + if err := json.Unmarshal([]byte(response.GetData()), &nodes); err != nil { + return []Peer{}, fmt.Errorf("unmarshaling data for peers: %w", err) + } + + peers := []Peer{} + for _, node := range nodes { + if node.Name != cfg.Node.Name { + peers = append(peers, Peer{Name: fmt.Sprintf("%v.%v", node.Name, network), PrivateIPv4: node.Address, PrivateIPv6: node.Address6}) + } + } + + return peers, nil +} diff --git a/netclient/main.go b/netclient/main.go index d43e7e5f..12ace5b8 100644 --- a/netclient/main.go +++ b/netclient/main.go @@ -26,6 +26,11 @@ func main() { app.Usage = "Netmaker's netclient agent and CLI. Used to perform interactions with Netmaker server and set local WireGuard config." app.Version = "v0.8.4" + hostname, err := os.Hostname() + if err != nil { + hostname = "" + } + cliFlags := []cli.Flag{ &cli.StringFlag{ Name: "network", @@ -91,7 +96,7 @@ func main() { &cli.StringFlag{ Name: "name", EnvVars: []string{"NETCLIENT_NAME"}, - Value: "", + Value: hostname, Usage: "Identifiable name for machine within Netmaker network.", }, &cli.StringFlag{ diff --git a/scripts/netclient-install.sh b/scripts/netclient-install.sh index 28e03ef5..c59bc6c7 100755 --- a/scripts/netclient-install.sh +++ b/scripts/netclient-install.sh @@ -2,7 +2,7 @@ set -e if [[ $EUID -ne 0 ]]; then - echo "This script must be run as root" + echo "This script must be run as root" exit 1 fi