From d4bdace534500b37f156ba95fd6cda1bb66d23a4 Mon Sep 17 00:00:00 2001 From: Burak Sekili Date: Mon, 6 Nov 2023 19:03:38 +0300 Subject: [PATCH] - created k8s package for all k8s-related operations - created tyk package for all tyk-releated operations - add checks to identify existing organisations - moved license package content to pkg/ - delete preinstallation package since it only runs one function. - moved all configs to pkg/config Signed-off-by: Burak Sekili --- cmd/bootstrap-post/main.go | 93 ++++----- cmd/bootstrap-pre-delete/main.go | 14 +- cmd/bootstrap-pre-install/main.go | 15 +- go.mod | 8 +- go.sum | 4 +- helpers/operator.go | 150 -------------- helpers/organisation.go | 131 ------------ helpers/portal.go | 299 --------------------------- helpers/user.go | 164 --------------- k8s/client.go | 150 ++++++++++++++ k8s/dashboard.go | 96 +++++++++ k8s/operator.go | 120 +++++++++++ {predelete => k8s}/predelete.go | 68 +++--- {readiness => k8s}/readiness.go | 30 +-- {data => pkg/config}/config.go | 89 +------- {data => pkg/constants}/constants.go | 2 +- {license => pkg}/license.go | 2 +- preinstallation/preinstallation.go | 24 --- tyk/api/request.go | 57 +++++ tyk/api/response.go | 52 +++++ tyk/internal/constants/constants.go | 11 + tyk/organisation.go | 120 +++++++++++ tyk/portal.go | 204 ++++++++++++++++++ tyk/tyk.go | 20 ++ tyk/user.go | 159 ++++++++++++++ 25 files changed, 1109 insertions(+), 973 deletions(-) delete mode 100644 helpers/operator.go delete mode 100644 helpers/organisation.go delete mode 100644 helpers/portal.go delete mode 100644 helpers/user.go create mode 100644 k8s/client.go create mode 100644 k8s/dashboard.go create mode 100644 k8s/operator.go rename {predelete => k8s}/predelete.go (54%) rename {readiness => k8s}/readiness.go (71%) rename {data => pkg/config}/config.go (63%) rename {data => pkg/constants}/constants.go (95%) rename {license => pkg}/license.go (97%) delete mode 100644 preinstallation/preinstallation.go create mode 100644 tyk/api/request.go create mode 100644 tyk/api/response.go create mode 100644 tyk/internal/constants/constants.go create mode 100644 tyk/organisation.go create mode 100644 tyk/portal.go create mode 100644 tyk/tyk.go create mode 100644 tyk/user.go diff --git a/cmd/bootstrap-post/main.go b/cmd/bootstrap-post/main.go index 7ad30c1..b047441 100644 --- a/cmd/bootstrap-post/main.go +++ b/cmd/bootstrap-post/main.go @@ -1,80 +1,81 @@ package main import ( - "crypto/tls" + "errors" "fmt" - "net/http" "os" - "tyk/tyk/bootstrap/data" - "tyk/tyk/bootstrap/helpers" - "tyk/tyk/bootstrap/readiness" + "tyk/tyk/bootstrap/k8s" + "tyk/tyk/bootstrap/pkg/config" + "tyk/tyk/bootstrap/tyk" ) func main() { - err := data.InitPostInstall() + conf, err := config.NewConfig() if err != nil { - fmt.Println(err) - os.Exit(1) + exit(err) } - err = readiness.CheckIfRequiredDeploymentsAreReady() + k8sClient, err := k8s.NewClient(conf) if err != nil { - fmt.Println(err) - os.Exit(1) + exit(err) } - tp := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: data.BootstrapConf.InsecureSkipVerify}, + if err = k8sClient.CheckIfRequiredDeploymentsAreReady(); err != nil { + exit(err) } - client := http.Client{Transport: tp} - fmt.Println("Started creating dashboard org") + orgExists := false - err = helpers.CheckForExistingOrganisation(client) - if err != nil { - fmt.Println(err) - os.Exit(1) + tykSvc := tyk.NewService(conf) + if err = tykSvc.OrgExists(); err != nil { + if !errors.Is(err, tyk.ErrOrgExists) { + exit(err) + } + + orgExists = true } - fmt.Println("Finished creating dashboard org") - fmt.Println("Generating dashboard credentials") + if !orgExists { + if err = tykSvc.CreateOrganisation(); err != nil { + exit(err) + } - err = helpers.GenerateDashboardCredentials(client) - if err != nil { - fmt.Println(err) - os.Exit(1) - } + if err = tykSvc.CreateAdmin(); err != nil { + exit(err) + } - fmt.Println("Finished generating dashboard credentials") - fmt.Println("Started bootstrapping operator secret") + if conf.BootstrapPortal { + if err = tykSvc.BootstrapClassicPortal(); err != nil { + exit(err) + } + } - if data.BootstrapConf.OperatorKubernetesSecretName != "" { - err = helpers.BootstrapTykOperatorSecret() - if err != nil { - fmt.Println(err) - os.Exit(1) + if err = k8sClient.RestartDashboard(); err != nil { + exit(err) } } - fmt.Println("Finished bootstrapping operator secret\nStarted bootstrapping portal secret") - - if data.BootstrapConf.DevPortalKubernetesSecretName != "" { - err = helpers.BootstrapTykPortalSecret() + if conf.OperatorKubernetesSecretName != "" { + err = k8sClient.BootstrapTykOperatorSecret() if err != nil { - fmt.Println(err) - os.Exit(1) + exit(err) } } - fmt.Println("Started bootstrapping portal with requests to dashboard") - - if data.BootstrapConf.BootstrapPortal { - err = helpers.BoostrapPortal(client) + if conf.DevPortalKubernetesSecretName != "" { + err = k8sClient.BootstrapTykPortalSecret() if err != nil { - fmt.Println(err) - os.Exit(1) + exit(err) } } - fmt.Println("Finished bootstrapping portal") +} + +func exit(err error) { + if err == nil { + os.Exit(0) + } + + fmt.Printf("[ERROR]: %v", err) + os.Exit(1) } diff --git a/cmd/bootstrap-pre-delete/main.go b/cmd/bootstrap-pre-delete/main.go index bde3d9c..3d64e44 100644 --- a/cmd/bootstrap-pre-delete/main.go +++ b/cmd/bootstrap-pre-delete/main.go @@ -3,18 +3,24 @@ package main import ( "fmt" "os" - "tyk/tyk/bootstrap/data" - "tyk/tyk/bootstrap/predelete" + "tyk/tyk/bootstrap/k8s" + "tyk/tyk/bootstrap/pkg/config" ) func main() { - err := data.InitBootstrapConf() + conf, err := config.NewConfig() if err != nil { fmt.Println(err) os.Exit(1) } - err = predelete.ExecutePreDeleteOperations() + k8sClient, err := k8s.NewClient(conf) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + err = k8sClient.ExecutePreDeleteOperations() if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/bootstrap-pre-install/main.go b/cmd/bootstrap-pre-install/main.go index 85d1b06..938c2a8 100644 --- a/cmd/bootstrap-pre-install/main.go +++ b/cmd/bootstrap-pre-install/main.go @@ -3,20 +3,23 @@ package main import ( "fmt" "os" - "tyk/tyk/bootstrap/data" - "tyk/tyk/bootstrap/preinstallation" + "tyk/tyk/bootstrap/pkg" + "tyk/tyk/bootstrap/pkg/config" ) func main() { - err := data.InitBootstrapConf() + conf, err := config.NewConfig() if err != nil { - fmt.Printf("Failed to parse bootstrap environment variables, err: %v", err) os.Exit(1) } - err = preinstallation.PreHookInstall() + licenseIsValid, err := pkg.ValidateDashboardLicense(conf.Tyk.DashboardLicense) if err != nil { - fmt.Printf("Failed to run pre-hook job, err: %v", err) + os.Exit(1) + } + + if !licenseIsValid { + //return errors.New("provided license is invalid") os.Exit(1) } diff --git a/go.mod b/go.mod index bb5c161..1fb1ae8 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.17 require ( github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/kelseyhightower/envconfig v1.4.0 + k8s.io/api v0.24.3 k8s.io/apimachinery v0.24.3 k8s.io/client-go v0.24.3 ) @@ -22,18 +24,19 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.6 // indirect github.com/google/gofuzz v1.1.0 // indirect + github.com/imdario/mergo v0.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/ginkgo v1.16.4 // indirect github.com/onsi/gomega v1.13.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect @@ -43,7 +46,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/api v0.24.3 // indirect k8s.io/klog/v2 v2.60.1 // indirect k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect diff --git a/go.sum b/go.sum index f928729..c7cf809 100644 --- a/go.sum +++ b/go.sum @@ -182,6 +182,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -415,8 +416,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/helpers/operator.go b/helpers/operator.go deleted file mode 100644 index a539663..0000000 --- a/helpers/operator.go +++ /dev/null @@ -1,150 +0,0 @@ -package helpers - -import ( - "context" - "fmt" - "tyk/tyk/bootstrap/data" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" -) - -func BootstrapTykOperatorSecret() error { - config, err := rest.InClusterConfig() - if err != nil { - return err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return err - } - - secrets, err := clientset. - CoreV1(). - Secrets(data.BootstrapConf.K8s.ReleaseNamespace). - List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return err - } - - for i := range secrets.Items { - secret := secrets.Items[i] - if secret.Name == data.BootstrapConf.OperatorKubernetesSecretName { - err = clientset. - CoreV1(). - Secrets(data.BootstrapConf.K8s.ReleaseNamespace). - Delete(context.TODO(), secret.Name, metav1.DeleteOptions{}) - - if err != nil { - return err - } - - fmt.Println("A previously created operator secret was identified and deleted") - - break - } - } - - err = CreateTykOperatorSecret(clientset) - if err != nil { - return err - } - - return nil -} - -func CreateTykOperatorSecret(clientset *kubernetes.Clientset) error { - secretData := map[string][]byte{ - TykAuth: []byte(data.BootstrapConf.Tyk.Admin.Auth), - TykOrg: []byte(data.BootstrapConf.Tyk.Org.ID), - TykMode: []byte(TykModePro), - TykUrl: []byte(data.BootstrapConf.K8s.DashboardSvcUrl), - } - - objectMeta := metav1.ObjectMeta{Name: data.BootstrapConf.OperatorKubernetesSecretName} - - secret := v1.Secret{ - ObjectMeta: objectMeta, - Data: secretData, - } - - _, err := clientset. - CoreV1(). - Secrets(data.BootstrapConf.K8s.ReleaseNamespace). - Create(context.TODO(), &secret, metav1.CreateOptions{}) - if err != nil { - return err - } - - return nil -} - -func BootstrapTykPortalSecret() error { - config, err := rest.InClusterConfig() - if err != nil { - return err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return err - } - - secrets, err := clientset.CoreV1().Secrets(data.BootstrapConf.K8s.ReleaseNamespace). - List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return err - } - - for i := range secrets.Items { - secret := secrets.Items[i] - - if data.BootstrapConf.DevPortalKubernetesSecretName == secret.Name { - err = clientset.CoreV1().Secrets(data.BootstrapConf.K8s.ReleaseNamespace). - Delete(context.TODO(), secret.Name, metav1.DeleteOptions{}) - if err != nil { - return err - } - - fmt.Println("A previously created portal secret was identified and deleted") - - break - } - } - - if data.BootstrapConf.DevPortalKubernetesSecretName != "" { - err = CreateTykPortalSecret(clientset, data.BootstrapConf.DevPortalKubernetesSecretName) - if err != nil { - return err - } - } - - return nil -} - -func CreateTykPortalSecret(clientset *kubernetes.Clientset, secretName string) error { - secretData := map[string][]byte{ - TykAuth: []byte(data.BootstrapConf.Tyk.Admin.Auth), - TykOrg: []byte(data.BootstrapConf.Tyk.Org.ID), - } - - objectMeta := metav1.ObjectMeta{Name: secretName} - - secret := v1.Secret{ - ObjectMeta: objectMeta, - Data: secretData, - } - - _, err := clientset. - CoreV1(). - Secrets(data.BootstrapConf.K8s.ReleaseNamespace). - Create(context.TODO(), &secret, metav1.CreateOptions{}) - if err != nil { - return err - } - - return nil -} diff --git a/helpers/organisation.go b/helpers/organisation.go deleted file mode 100644 index 54e29df..0000000 --- a/helpers/organisation.go +++ /dev/null @@ -1,131 +0,0 @@ -package helpers - -import ( - "bytes" - "errors" - "fmt" - "io" - "net/http" - "tyk/tyk/bootstrap/data" - - "k8s.io/apimachinery/pkg/util/json" -) - -const ( - AdminOrganisationsEndpoint = "/admin/organisations" - ApiUsersActionsResetEndpoint = "%s/api/users/%s/actions/reset" - ApiPortalCatalogueEndpoint = "/api/portal/catalogue" - ApiPortalPagesEndpoint = "/api/portal/pages" - ApiPortalConfigurationEndpoint = "/api/portal/configuration" - ApiPortalCnameEndpoint = "/api/portal/cname" - - TykModePro = "pro" - TykAuth = "TYK_AUTH" - TykOrg = "TYK_ORG" - TykMode = "TYK_MODE" - TykUrl = "TYK_URL" -) - -func CheckForExistingOrganisation(client http.Client) error { - fmt.Println("Checking for existing organisations") - - orgsApiEndpoint := data.BootstrapConf.K8s.DashboardSvcUrl + AdminOrganisationsEndpoint - - req, err := http.NewRequest(http.MethodGet, orgsApiEndpoint, nil) - if err != nil { - return err - } - - req.Header.Set(data.AdminAuthHeader, data.BootstrapConf.Tyk.Admin.Secret) - req.Header.Set(data.ContentTypeHeader, "application/json") - - res, err := client.Do(req) - if err != nil { - return err - } - - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - } - - orgs := OrgResponse{} - - err = json.Unmarshal(bodyBytes, &orgs) - if err != nil { - return err - } - - if len(orgs.Organisations) > 0 { - for _, organisation := range orgs.Organisations { - if organisation["owner_name"] == data.BootstrapConf.Tyk.Org.Name || - organisation["cname"] == data.BootstrapConf.Tyk.Org.Cname { - return errors.New("there shouldn't be any organisations, please " + - "disable bootstrapping to avoid losing data or delete " + - "already existing organisations") - } - } - } else { - fmt.Println("No organisations have been detected, we can proceed") - return nil - } - - return nil -} - -type CreateOrgStruct struct { - OwnerName string `json:"owner_name"` - CnameEnabled bool `json:"cname_enabled"` - Cname string `json:"cname"` -} - -func CreateOrganisation(client http.Client, dashBoardUrl string) (string, error) { - createOrgData := CreateOrgStruct{ - OwnerName: data.BootstrapConf.Tyk.Org.Name, - CnameEnabled: true, - Cname: data.BootstrapConf.Tyk.Org.Cname, - } - - reqBodyBytes, err := json.Marshal(createOrgData) - if err != nil { - return "", err - } - - req, err := http.NewRequest(http.MethodPost, dashBoardUrl+AdminOrganisationsEndpoint, bytes.NewReader(reqBodyBytes)) - if err != nil { - return "", err - } - - req.Header.Set(data.AdminAuthHeader, data.BootstrapConf.Tyk.Admin.Secret) - req.Header.Set(data.ContentTypeHeader, "application/json") - - res, err := client.Do(req) - if err != nil { - return "", err - } - - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - } - - createOrgResponse := DashboardGeneralResponse{} - - err = json.Unmarshal(bodyBytes, &createOrgResponse) - if err != nil { - return "", err - } - - return createOrgResponse.Meta, nil -} - -type DashboardGeneralResponse struct { - Status string `json:"Status"` - Message string `json:"Message"` - Meta string `json:"Meta"` -} - -type OrgResponse struct { - Organisations []map[string]interface{} `json:"organisations"` - Pages int `json:"pages"` -} diff --git a/helpers/portal.go b/helpers/portal.go deleted file mode 100644 index 7006496..0000000 --- a/helpers/portal.go +++ /dev/null @@ -1,299 +0,0 @@ -package helpers - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "net/http" - "time" - "tyk/tyk/bootstrap/data" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/json" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" -) - -func BoostrapPortal(client http.Client) error { - err := CreatePortalDefaultSettings(client) - if err != nil { - return err - } - - err = InitialiseCatalogue(client) - if err != nil { - return err - } - - err = CreatePortalHomepage(client) - if err != nil { - return err - } - - err = SetPortalCname(client) - if err != nil { - return err - } - - return nil -} - -type InitCatalogReq struct { - OrgId string `json:"org_id"` -} - -type CnameRequest struct { - Cname string `json:"cname"` -} - -func SetPortalCname(client http.Client) error { - fmt.Println("Setting portal cname") - - cnameReq := CnameRequest{Cname: data.BootstrapConf.Tyk.Org.Cname} - - reqBody, err := json.Marshal(cnameReq) - if err != nil { - return err - } - - req, err := http.NewRequest( - http.MethodPut, - data.BootstrapConf.K8s.DashboardSvcUrl+ApiPortalCnameEndpoint, - bytes.NewReader(reqBody), - ) - if err != nil { - return err - } - - req.Header.Set(data.AuthorizationHeader, data.BootstrapConf.Tyk.Admin.Auth) - - res, err := client.Do(req) - if err != nil { - return err - } - - if res.StatusCode != http.StatusOK { - return errors.New("failed to set portal cname") - } - - // restarting the dashboard to apply the new cname - return RestartDashboard() -} - -func InitialiseCatalogue(client http.Client) error { - fmt.Println("Initialising Catalogue") - - initCatalog := InitCatalogReq{OrgId: data.BootstrapConf.Tyk.Org.ID} - - reqBody, err := json.Marshal(initCatalog) - if err != nil { - return err - } - - req, err := http.NewRequest( - http.MethodPost, - data.BootstrapConf.K8s.DashboardSvcUrl+ApiPortalCatalogueEndpoint, - bytes.NewReader(reqBody), - ) - if err != nil { - return err - } - - req.Header.Set(data.AuthorizationHeader, data.BootstrapConf.Tyk.Admin.Auth) - - res, err := client.Do(req) - if err != nil || res.StatusCode != http.StatusOK { - return err - } - - resp := DashboardGeneralResponse{} - - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - } - - err = json.Unmarshal(bodyBytes, &resp) - if err != nil { - return err - } - - return nil -} - -func CreatePortalHomepage(client http.Client) error { - fmt.Println("Creating portal homepage") - - homepageContents := GetPortalHomepage() - - reqBody, err := json.Marshal(homepageContents) - if err != nil { - return err - } - - reqData := bytes.NewReader(reqBody) - - req, err := http.NewRequest(http.MethodPost, data.BootstrapConf.K8s.DashboardSvcUrl+ApiPortalPagesEndpoint, reqData) - if err != nil { - return err - } - - req.Header.Set(data.AuthorizationHeader, data.BootstrapConf.Tyk.Admin.Auth) - - res, err := client.Do(req) - if err != nil || res.StatusCode != http.StatusOK { - return err - } - - resp := DashboardGeneralResponse{} - - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - } - - err = json.Unmarshal(bodyBytes, &resp) - if err != nil { - return err - } - - return nil -} - -func GetPortalHomepage() PortalHomepageRequest { - return PortalHomepageRequest{ - IsHomepage: true, - TemplateName: "", - Title: "Developer portal name", - Slug: "/", - Fields: PortalFields{ - JumboCTATitle: "Tyk Developer Portal", - SubHeading: "Sub Header", - JumboCTALink: "#cta", - JumboCTALinkTitle: "Your awesome APIs, hosted with Tyk!", - PanelOneContent: "Panel 1 content.", - PanelOneLink: "#panel1", - PanelOneLinkTitle: "Panel 1 Button", - PanelOneTitle: "Panel 1 Title", - PanelThereeContent: "", - PanelThreeContent: "Panel 3 content.", - PanelThreeLink: "#panel3", - PanelThreeLinkTitle: "Panel 3 Button", - PanelThreeTitle: "Panel 3 Title", - PanelTwoContent: "Panel 2 content.", - PanelTwoLink: "#panel2", - PanelTwoLinkTitle: "Panel 2 Button", - PanelTwoTitle: "Panel 2 Title", - }, - } -} - -type PortalHomepageRequest struct { - IsHomepage bool `json:"is_homepage"` - TemplateName string `json:"template_name"` - Title string `json:"title"` - Slug string `json:"slug"` - Fields PortalFields `json:"fields"` -} - -type PortalFields struct { - JumboCTATitle string `json:"JumboCTATitle"` - SubHeading string `json:"SubHeading"` - JumboCTALink string `json:"JumboCTALink"` - JumboCTALinkTitle string `json:"JumboCTALinkTitle"` - PanelOneContent string `json:"PanelOneContent"` - PanelOneLink string `json:"PanelOneLink"` - PanelOneLinkTitle string `json:"PanelOneLinkTitle"` - PanelOneTitle string `json:"PanelOneTitle"` - PanelThereeContent string `json:"PanelThereeContent"` - PanelThreeContent string `json:"PanelThreeContent"` - PanelThreeLink string `json:"PanelThreeLink"` - PanelThreeLinkTitle string `json:"PanelThreeLinkTitle"` - PanelThreeTitle string `json:"PanelThreeTitle"` - PanelTwoContent string `json:"PanelTwoContent"` - PanelTwoLink string `json:"PanelTwoLink"` - PanelTwoLinkTitle string `json:"PanelTwoLinkTitle"` - PanelTwoTitle string `json:"PanelTwoTitle"` -} - -func CreatePortalDefaultSettings(client http.Client) error { - fmt.Println("Creating bootstrap default settings") - - // TODO(buraksekili): DashboardSvcUrl can be populated via environment variables. So, the URL - // might have trailing slashes. Constructing the URL with raw string concatenating is not a good - // approach here. Needs refactoring. - req, err := http.NewRequest( - http.MethodPut, - data.BootstrapConf.K8s.DashboardSvcUrl+ApiPortalConfigurationEndpoint, - nil, - ) - req.Header.Set(data.AuthorizationHeader, data.BootstrapConf.Tyk.Admin.Auth) - - if err != nil { - return err - } - - res, err := client.Do(req) - if err != nil || res.StatusCode != http.StatusOK { - return err - } - - return nil -} - -func RestartDashboard() error { - config, err := rest.InClusterConfig() - if err != nil { - return err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return err - } - - if data.BootstrapConf.K8s.DashboardDeploymentName == "" { - ls := metav1.LabelSelector{MatchLabels: map[string]string{ - data.TykBootstrapLabel: data.TykBootstrapDashboardDeployLabel, - }} - - deployments, err := clientset. - AppsV1(). - Deployments(data.BootstrapConf.K8s.ReleaseNamespace). - List( - context.TODO(), - metav1.ListOptions{ - LabelSelector: labels.Set(ls.MatchLabels).String(), - }, - ) - if err != nil { - return fmt.Errorf("failed to list Tyk Dashboard Deployment, err: %v", err) - } - - for i := range deployments.Items { - data.BootstrapConf.K8s.DashboardDeploymentName = deployments.Items[i].ObjectMeta.Name - } - } - - timeStamp := fmt.Sprintf( - `{"spec": {"template": {"metadata": {"annotations": {"kubectl.kubernetes.io/restartedAt": "%s"}}}}}`, - time.Now().Format("20060102150405"), - ) - - _, err = clientset. - AppsV1(). - Deployments(data.BootstrapConf.K8s.ReleaseNamespace). - Patch( - context.TODO(), - data.BootstrapConf.K8s.DashboardDeploymentName, - types.StrategicMergePatchType, - []byte(timeStamp), - metav1.PatchOptions{}, - ) - - return err -} diff --git a/helpers/user.go b/helpers/user.go deleted file mode 100644 index 354693c..0000000 --- a/helpers/user.go +++ /dev/null @@ -1,164 +0,0 @@ -package helpers - -import ( - "bytes" - "errors" - "fmt" - "io" - "net/http" - "time" - "tyk/tyk/bootstrap/data" - - "k8s.io/apimachinery/pkg/util/json" -) - -func CreateUser(client http.Client, dashboardUrl, orgId string) (string, error) { - userData, err := GetUserData(client, dashboardUrl, orgId) - if err != nil { - return "", err - } - - err = SetUserPassword(client, userData.UserId, userData.AuthCode, dashboardUrl) - if err != nil { - return "", err - } - - return userData.AuthCode, nil -} - -type ResetPasswordStruct struct { - NewPassword string `json:"new_password"` - UserPermissions map[string]string `json:"user_permissions"` -} - -func SetUserPassword(client http.Client, userId, authCode, dashboardUrl string) error { - newPasswordData := ResetPasswordStruct{ - NewPassword: data.BootstrapConf.Tyk.Admin.Password, - UserPermissions: map[string]string{"IsAdmin": "admin"}, - } - - reqBody, err := json.Marshal(newPasswordData) - if err != nil { - return err - } - - req, err := http.NewRequest( - http.MethodPost, - fmt.Sprintf(ApiUsersActionsResetEndpoint, dashboardUrl, userId), - bytes.NewReader(reqBody)) - if err != nil { - return err - } - - req.Header.Set(data.AuthorizationHeader, authCode) - req.Header.Set(data.ContentTypeHeader, "application/json") - - res, err := client.Do(req) - if err != nil { - return err - } - - if res.StatusCode != 200 { - return errors.New("resetting password did not work") - } - - return nil -} - -func GenerateDashboardCredentials(client http.Client) error { - orgId, err := CreateOrganisation(client, data.BootstrapConf.K8s.DashboardSvcUrl) - if err != nil { - return err - } - - data.BootstrapConf.Tyk.Org.ID = orgId - - userAuth, err := CreateUser(client, data.BootstrapConf.K8s.DashboardSvcUrl, orgId) - if err != nil { - return err - } - - data.BootstrapConf.Tyk.Admin.Auth = userAuth - - return nil -} - -type CreateUserRequest struct { - OrganisationId string `json:"org_id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - EmailAddress string `json:"email_address"` - Active bool `json:"active"` - UserPermissions map[string]string `json:"user_permissions"` -} - -type CreateUserResponse struct { - Status string `json:"Status"` - Message string `json:"Message"` - Meta struct { - APIModel struct{} `json:"api_model"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - EmailAddress string `json:"email_address"` - OrgID string `json:"org_id"` - Active bool `json:"active"` - ID string `json:"id"` - AccessKey string `json:"access_key"` - UserPermissions struct { - IsAdmin string `json:"IsAdmin"` - } `json:"user_permissions"` - GroupID string `json:"group_id"` - PasswordMaxDays int `json:"password_max_days"` - PasswordUpdated time.Time `json:"password_updated"` - PWHistory []interface{} `json:"PWHistory"` - CreatedAt time.Time `json:"created_at"` - } `json:"Meta"` -} - -type NeededUserData struct { - AuthCode string - UserId string -} - -func GetUserData(client http.Client, dashboardUrl, orgId string) (NeededUserData, error) { - reqBody := CreateUserRequest{ - OrganisationId: orgId, - FirstName: data.BootstrapConf.Tyk.Admin.FirstName, - LastName: data.BootstrapConf.Tyk.Admin.LastName, - EmailAddress: data.BootstrapConf.Tyk.Admin.EmailAddress, - Active: true, - UserPermissions: map[string]string{"IsAdmin": "admin"}, - } - - reqBytes, err := json.Marshal(reqBody) - if err != nil { - return NeededUserData{}, err - } - - req, err := http.NewRequest(http.MethodPost, dashboardUrl+"/admin/users", bytes.NewReader(reqBytes)) - if err != nil { - return NeededUserData{}, err - } - - req.Header.Set(data.AdminAuthHeader, data.BootstrapConf.Tyk.Admin.Secret) - req.Header.Set(data.ContentTypeHeader, "application/json") - - res, err := client.Do(req) - if err != nil { - return NeededUserData{}, err - } - - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - } - - getUserResponse := CreateUserResponse{} - - err = json.Unmarshal(bodyBytes, &getUserResponse) - if err != nil { - return NeededUserData{}, err - } - - return NeededUserData{UserId: getUserResponse.Meta.ID, AuthCode: getUserResponse.Message}, nil -} diff --git a/k8s/client.go b/k8s/client.go new file mode 100644 index 0000000..280e60d --- /dev/null +++ b/k8s/client.go @@ -0,0 +1,150 @@ +package k8s + +import ( + "fmt" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "tyk/tyk/bootstrap/pkg/config" +) + +type Client struct { + appArgs *config.Config + clientSet *kubernetes.Clientset +} + +// NewClient returns a new Client to interact with Kubernetes. It first tries to instantiate in-cluster client; +// otherwise, returns client via reading default kubeconfig located /$HOME/.kube/config +func NewClient(conf *config.Config) (*Client, error) { + cl := &Client{} + + var err error + var config *rest.Config + + config, err = rest.InClusterConfig() + if err != nil { + config, err = clientcmd.BuildConfigFromFlags("", clientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename()) + if err != nil { + return nil, err + } + } + + cs, err := kubernetes.NewForConfig(config) + if err != nil { + fmt.Printf("failed to generate client, err: %v", err) + return nil, err + } + + cl.clientSet = cs + + if conf.BootstrapDashboard { + dashURL, err := cl.discoverDashboardSvc() + if err != nil { + return nil, err + } + + conf.K8s.DashboardSvcUrl = dashURL + } + + cl.appArgs = conf + + return cl, nil +} + +//func (c *Client) RestartDashboardDeployment() error { +// config, err := rest.InClusterConfig() +// if err != nil { +// return err +// } +// +// clientset, err := kubernetes.NewForConfig(config) +// if err != nil { +// return err +// } +// +// if c.AppArgs.DashboardDeploymentName == "" { +// ls := metav1.LabelSelector{MatchLabels: map[string]string{ +// data.TykBootstrapLabel: data.TykBootstrapDashboardDeployLabel, +// }} +// +// if c.AppArgs.ReleaseName != "" { +// ls.MatchLabels[data.TykBootstrapReleaseLabel] = c.AppArgs.ReleaseName +// } +// +// deployments, err := clientset. +// AppsV1(). +// Deployments(c.AppArgs.ReleaseNamespace). +// List( +// context.TODO(), +// metav1.ListOptions{ +// LabelSelector: labels.Set(ls.MatchLabels).String(), +// }, +// ) +// if err != nil { +// return errors.New(fmt.Sprintf("failed to list Tyk Dashboard Deployment, err: %v", err)) +// } +// +// for _, deployment := range deployments.Items { +// c.AppArgs.DashboardDeploymentName = deployment.ObjectMeta.Name +// } +// } +// +// timeStamp := fmt.Sprintf(`{"spec": {"template": {"metadata": {"annotations": {"kubectl.kubernetes.io/restartedAt": "%s"}}}}}`, +// time.Now().Format("20060102150405")) +// +// _, err = clientset. +// AppsV1(). +// Deployments(c.AppArgs.ReleaseName). +// Patch( +// context.TODO(), +// c.AppArgs.DashboardDeploymentName, +// types.StrategicMergePatchType, +// []byte(timeStamp), +// metav1.PatchOptions{}, +// ) +// +// return err +//} +// +//// discoverDashboardSvc lists Service objects with TykBootstrapReleaseLabel label that has +//// TykBootstrapDashboardSvcLabel value and gets this Service's metadata name, and port and +//// updates DashboardSvcName and DashboardSvcPort fields. +//func (c *Client) discoverDashboardSvc() error { +// ls := metav1.LabelSelector{MatchLabels: map[string]string{ +// data.TykBootstrapLabel: data.TykBootstrapDashboardSvcLabel, +// }} +// if c.AppArgs.ReleaseName != "" { +// ls.MatchLabels[data.TykBootstrapReleaseLabel] = c.AppArgs.ReleaseName +// } +// +// l := labels.Set(ls.MatchLabels).String() +// +// services, err := c.clientSet. +// CoreV1(). +// Services(c.AppArgs.ReleaseNamespace). +// List(context.TODO(), metav1.ListOptions{LabelSelector: l}) +// if err != nil { +// return err +// } +// +// if len(services.Items) == 0 { +// return fmt.Errorf("failed to find services with label %v\n", l) +// } +// +// if len(services.Items) > 1 { +// fmt.Printf("[WARNING] Found multiple services with label %v\n", l) +// } +// +// service := services.Items[0] +// if len(service.Spec.Ports) == 0 { +// return fmt.Errorf("svc/%v/%v has no open ports\n", service.Name, service.Namespace) +// } +// if len(service.Spec.Ports) > 1 { +// fmt.Printf("[WARNING] Found multiple open ports in svc/%v/%v\n", service.Name, service.Namespace) +// } +// +// c.AppArgs.DashboardSvcPort = service.Spec.Ports[0].Port +// c.AppArgs.DashboardSvcName = service.Name +// +// return nil +//} diff --git a/k8s/dashboard.go b/k8s/dashboard.go new file mode 100644 index 0000000..e16c39d --- /dev/null +++ b/k8s/dashboard.go @@ -0,0 +1,96 @@ +package k8s + +import ( + "context" + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "time" + "tyk/tyk/bootstrap/pkg/constants" +) + +func (c *Client) RestartDashboard() error { + if c.appArgs.K8s.DashboardDeploymentName == "" { + ls := metav1.LabelSelector{MatchLabels: map[string]string{ + constants.TykBootstrapLabel: constants.TykBootstrapDashboardDeployLabel, + }} + + deployments, err := c.clientSet. + AppsV1(). + Deployments(c.appArgs.K8s.ReleaseNamespace). + List( + context.TODO(), + metav1.ListOptions{ + LabelSelector: labels.Set(ls.MatchLabels).String(), + }, + ) + if err != nil { + return fmt.Errorf("failed to list Tyk Dashboard Deployment, err: %v", err) + } + + for i := range deployments.Items { + c.appArgs.K8s.DashboardDeploymentName = deployments.Items[i].ObjectMeta.Name + } + } + + timeStamp := fmt.Sprintf( + `{"spec": {"template": {"metadata": {"annotations": {"kubectl.kubernetes.io/restartedAt": "%s"}}}}}`, + time.Now().Format("20060102150405"), + ) + + _, err := c.clientSet. + AppsV1(). + Deployments(c.appArgs.K8s.ReleaseNamespace). + Patch( + context.TODO(), + c.appArgs.K8s.DashboardDeploymentName, + types.StrategicMergePatchType, + []byte(timeStamp), + metav1.PatchOptions{}, + ) + + return err +} + +// discoverDashboardSvc lists Service objects with constants.TykBootstrapReleaseLabel label that has +// constants.TykBootstrapDashboardSvcLabel value and returns a service URL for Tyk Dashboard. +func (c *Client) discoverDashboardSvc() (string, error) { + ls := metav1.LabelSelector{MatchLabels: map[string]string{ + constants.TykBootstrapLabel: constants.TykBootstrapDashboardSvcLabel, + }} + + l := labels.Set(ls.MatchLabels).String() + + services, err := c.clientSet. + CoreV1(). + Services(c.appArgs.K8s.ReleaseNamespace). + List(context.TODO(), metav1.ListOptions{LabelSelector: l}) + if err != nil { + return "", err + } + + if len(services.Items) == 0 { + return "", fmt.Errorf("failed to find services with label %v\n", l) + } + + if len(services.Items) > 1 { + fmt.Printf("[WARNING] Found multiple services with label %v\n", l) + } + + service := services.Items[0] + if len(service.Spec.Ports) == 0 { + return "", fmt.Errorf("svc/%v/%v has no open ports\n", service.Name, service.Namespace) + } + + if len(service.Spec.Ports) > 1 { + fmt.Printf("[WARNING] Found multiple open ports in svc/%v/%v\n", service.Name, service.Namespace) + } + + return fmt.Sprintf("%s://%s.%s.svc.cluster.local:%d", + c.appArgs.K8s.DashboardSvcProto, + service.Name, + c.appArgs.K8s.ReleaseNamespace, + service.Spec.Ports[0].Port, + ), nil +} diff --git a/k8s/operator.go b/k8s/operator.go new file mode 100644 index 0000000..40f5ad8 --- /dev/null +++ b/k8s/operator.go @@ -0,0 +1,120 @@ +package k8s + +import ( + "context" + "fmt" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + TykModePro = "pro" + TykAuth = "TYK_AUTH" + TykOrg = "TYK_ORG" + TykMode = "TYK_MODE" + TykUrl = "TYK_URL" +) + +func (c *Client) BootstrapTykOperatorSecret() error { + secrets, err := c.clientSet. + CoreV1(). + Secrets(c.appArgs.K8s.ReleaseNamespace). + List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + + for i := range secrets.Items { + secret := secrets.Items[i] + if secret.Name == c.appArgs.OperatorKubernetesSecretName { + err = c.clientSet. + CoreV1(). + Secrets(c.appArgs.K8s.ReleaseNamespace). + Delete(context.TODO(), secret.Name, metav1.DeleteOptions{}) + + if err != nil { + return err + } + + fmt.Println("A previously created operator secret was identified and deleted") + + break + } + } + + secretData := map[string][]byte{ + TykAuth: []byte(c.appArgs.Tyk.Admin.Auth), + TykOrg: []byte(c.appArgs.Tyk.Org.ID), + TykMode: []byte(TykModePro), + TykUrl: []byte(c.appArgs.K8s.DashboardSvcUrl), + } + + objectMeta := metav1.ObjectMeta{Name: c.appArgs.OperatorKubernetesSecretName} + + secret := v1.Secret{ + ObjectMeta: objectMeta, + Data: secretData, + } + + _, err = c.clientSet. + CoreV1(). + Secrets(c.appArgs.K8s.ReleaseNamespace). + Create(context.TODO(), &secret, metav1.CreateOptions{}) + if err != nil { + return err + } + + return nil +} + +func (c *Client) BootstrapTykPortalSecret() error { + secrets, err := c.clientSet. + CoreV1(). + Secrets(c.appArgs.K8s.ReleaseNamespace). + List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + + for i := range secrets.Items { + secret := secrets.Items[i] + + if c.appArgs.DevPortalKubernetesSecretName == secret.Name { + err = c.clientSet. + CoreV1(). + Secrets(c.appArgs.K8s.ReleaseNamespace). + Delete(context.TODO(), secret.Name, metav1.DeleteOptions{}) + if err != nil { + return err + } + + fmt.Println("A previously created portal secret was identified and deleted") + + break + } + } + + if c.appArgs.DevPortalKubernetesSecretName != "" { + secretData := map[string][]byte{ + TykAuth: []byte(c.appArgs.Tyk.Admin.Auth), + TykOrg: []byte(c.appArgs.Tyk.Org.ID), + } + + objectMeta := metav1.ObjectMeta{Name: c.appArgs.DevPortalKubernetesSecretName} + + secret := v1.Secret{ + ObjectMeta: objectMeta, + Data: secretData, + } + + _, err := c.clientSet. + CoreV1(). + Secrets(c.appArgs.K8s.ReleaseNamespace). + Create(context.TODO(), &secret, metav1.CreateOptions{}) + if err != nil { + return err + } + } + + return nil +} diff --git a/predelete/predelete.go b/k8s/predelete.go similarity index 54% rename from predelete/predelete.go rename to k8s/predelete.go index 2ac7e47..a26aaca 100644 --- a/predelete/predelete.go +++ b/k8s/predelete.go @@ -1,51 +1,35 @@ -package predelete +package k8s import ( "context" "fmt" - "tyk/tyk/bootstrap/data" + "tyk/tyk/bootstrap/pkg/constants" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" ) -// maybe not needed? -func ExecutePreDeleteOperations() error { - config, err := rest.InClusterConfig() - if err != nil { - return err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return err - } - - err = PreDeleteOperatorSecret(clientset) - if err != nil { +func (c *Client) ExecutePreDeleteOperations() error { + if err := c.deleteOperatorSecret(); err != nil { return err } - err = PreDeletePortalSecret(clientset) - if err != nil { + if err := c.deletePortalSecret(); err != nil { return err } - err = PreDeleteBootstrappingJobs(clientset) - if err != nil { + if err := c.deleteBootstrappingJobs(); err != nil { return err } return nil } -func PreDeleteOperatorSecret(clientset *kubernetes.Clientset) error { +func (c *Client) deleteOperatorSecret() error { fmt.Println("Running pre delete hook") - secrets, err := clientset. + secrets, err := c.clientSet. CoreV1(). - Secrets(data.BootstrapConf.K8s.ReleaseNamespace). + Secrets(c.appArgs.K8s.ReleaseNamespace). List(context.TODO(), metav1.ListOptions{}) if err != nil { return err @@ -55,10 +39,10 @@ func PreDeleteOperatorSecret(clientset *kubernetes.Clientset) error { for i := range secrets.Items { value := secrets.Items[i] - if value.Name == data.BootstrapConf.OperatorKubernetesSecretName { - err = clientset. + if value.Name == c.appArgs.OperatorKubernetesSecretName { + err = c.clientSet. CoreV1(). - Secrets(data.BootstrapConf.K8s.ReleaseNamespace). + Secrets(c.appArgs.K8s.ReleaseNamespace). Delete(context.TODO(), value.Name, metav1.DeleteOptions{}) if err != nil { @@ -80,10 +64,12 @@ func PreDeleteOperatorSecret(clientset *kubernetes.Clientset) error { return nil } -func PreDeletePortalSecret(clientset *kubernetes.Clientset) error { +func (c *Client) deletePortalSecret() error { fmt.Println("Running pre delete hook") - secrets, err := clientset.CoreV1().Secrets(data.BootstrapConf.K8s.ReleaseNamespace). + secrets, err := c.clientSet. + CoreV1(). + Secrets(c.appArgs.K8s.ReleaseNamespace). List(context.TODO(), metav1.ListOptions{}) if err != nil { return err @@ -93,8 +79,8 @@ func PreDeletePortalSecret(clientset *kubernetes.Clientset) error { for i := range secrets.Items { value := secrets.Items[i] - if data.BootstrapConf.DevPortalKubernetesSecretName == value.Name { - err = clientset.CoreV1().Secrets(data.BootstrapConf.K8s.ReleaseNamespace). + if c.appArgs.DevPortalKubernetesSecretName == value.Name { + err = c.clientSet.CoreV1().Secrets(c.appArgs.K8s.ReleaseNamespace). Delete(context.TODO(), value.Name, metav1.DeleteOptions{}) if err != nil { @@ -116,16 +102,16 @@ func PreDeletePortalSecret(clientset *kubernetes.Clientset) error { return nil } -// PreDeleteBootstrappingJobs deletes all jobs within the release namespace, that has specific label. -func PreDeleteBootstrappingJobs(clientset *kubernetes.Clientset) error { +// deleteBootstrappingJobs deletes all jobs within the release namespace, that has specific label. +func (c *Client) deleteBootstrappingJobs() error { // Usually, the raw strings in label selectors are not recommended. - jobs, err := clientset. + jobs, err := c.clientSet. BatchV1(). - Jobs(data.BootstrapConf.K8s.ReleaseNamespace). + Jobs(c.appArgs.K8s.ReleaseNamespace). List( context.TODO(), metav1.ListOptions{ - LabelSelector: data.TykBootstrapLabel, + LabelSelector: constants.TykBootstrapLabel, }, ) if err != nil { @@ -138,13 +124,13 @@ func PreDeleteBootstrappingJobs(clientset *kubernetes.Clientset) error { job := jobs.Items[i] // Do not need to delete pre-delete job. It will be deleted by Helm. - jobLabel := job.ObjectMeta.Labels[data.TykBootstrapLabel] - if jobLabel != data.TykBootstrapPreDeleteLabel { + jobLabel := job.ObjectMeta.Labels[constants.TykBootstrapLabel] + if jobLabel != constants.TykBootstrapPreDeleteLabel { deletePropagationType := metav1.DeletePropagationBackground - err2 := clientset. + err2 := c.clientSet. BatchV1(). - Jobs(data.BootstrapConf.K8s.ReleaseNamespace). + Jobs(c.appArgs.K8s.ReleaseNamespace). Delete(context.TODO(), job.Name, metav1.DeleteOptions{PropagationPolicy: &deletePropagationType}) if err2 != nil { errCascading = err2 diff --git a/readiness/readiness.go b/k8s/readiness.go similarity index 71% rename from readiness/readiness.go rename to k8s/readiness.go index 8df5556..d053276 100644 --- a/readiness/readiness.go +++ b/k8s/readiness.go @@ -1,32 +1,18 @@ -package readiness +package k8s import ( "context" "errors" "fmt" - "strings" - "time" - "tyk/tyk/bootstrap/data" - v1 "k8s.io/api/core/v1" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" + "strings" + "time" ) -func CheckIfRequiredDeploymentsAreReady() error { - config, err := rest.InClusterConfig() - if err != nil { - return err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return err - } - +func (c *Client) CheckIfRequiredDeploymentsAreReady() error { time.Sleep(5 * time.Second) - var attemptCount int for { @@ -35,7 +21,9 @@ func CheckIfRequiredDeploymentsAreReady() error { return errors.New("attempted readiness check too many times") } - pods, err := clientset.CoreV1().Pods(data.BootstrapConf.K8s.ReleaseNamespace). + pods, err := c.clientSet. + CoreV1(). + Pods(c.appArgs.K8s.ReleaseNamespace). List(context.TODO(), metav1.ListOptions{}) if err != nil { return err @@ -72,7 +60,7 @@ func CheckIfRequiredDeploymentsAreReady() error { fmt.Printf("The following pods have containers that are NOT ready: ") - for podName, _ := range notReadyPods { + for podName := range notReadyPods { fmt.Println(podName) } diff --git a/data/config.go b/pkg/config/config.go similarity index 63% rename from data/config.go rename to pkg/config/config.go index 39ccf52..9f4a532 100644 --- a/data/config.go +++ b/pkg/config/config.go @@ -1,14 +1,7 @@ -package data +package config import ( - "context" - "fmt" - "github.com/kelseyhightower/envconfig" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" ) const prefix = "TYK_K8SBOOTSTRAP" @@ -68,7 +61,8 @@ type TykAdmin struct { // in Authorization header of the HTTP requests that will be sent to Tyk for bootstrapping. // Also, if bootstrapping Tyk Operator Secret is enabled Auth corresponds to TykAuth field in the // Kubernetes secret of Tyk Operator. - Auth string `ignored:"true"` + // Set it if Tyk Dashboard is already bootstrapped. + Auth string } type TykOrg struct { @@ -91,78 +85,11 @@ type TykConf struct { DashboardLicense string } -var BootstrapConf = Config{} - -func InitBootstrapConf() error { - return envconfig.Process(prefix, &BootstrapConf) -} - -func InitPostInstall() error { - err := InitBootstrapConf() - if err != nil { - return err - } - - if BootstrapConf.BootstrapDashboard { - dashURL, err := discoverDashboardSvc() - if err != nil { - return err - } - - BootstrapConf.K8s.DashboardSvcUrl = dashURL - } - - return nil -} - -// discoverDashboardSvc lists Service objects with constants.TykBootstrapReleaseLabel label that has -// constants.TykBootstrapDashboardSvcLabel value and returns a service URL for Tyk Dashboard. -func discoverDashboardSvc() (string, error) { - config, err := rest.InClusterConfig() - if err != nil { - return "", err - } - - c, err := kubernetes.NewForConfig(config) - if err != nil { - return "", err - } - - ls := metav1.LabelSelector{MatchLabels: map[string]string{ - TykBootstrapLabel: TykBootstrapDashboardSvcLabel, - }} - - l := labels.Set(ls.MatchLabels).String() - - services, err := c. - CoreV1(). - Services(BootstrapConf.K8s.ReleaseNamespace). - List(context.TODO(), metav1.ListOptions{LabelSelector: l}) - if err != nil { - return "", err - } - - if len(services.Items) == 0 { - return "", fmt.Errorf("failed to find services with label %v\n", l) - } - - if len(services.Items) > 1 { - fmt.Printf("[WARNING] Found multiple services with label %v\n", l) - } - - service := services.Items[0] - if len(service.Spec.Ports) == 0 { - return "", fmt.Errorf("svc/%v/%v has no open ports\n", service.Name, service.Namespace) - } - - if len(service.Spec.Ports) > 1 { - fmt.Printf("[WARNING] Found multiple open ports in svc/%v/%v\n", service.Name, service.Namespace) +func NewConfig() (*Config, error) { + conf := &Config{} + if err := envconfig.Process(prefix, conf); err != nil { + return nil, err } - return fmt.Sprintf("%s://%s.%s.svc.cluster.local:%d", - BootstrapConf.K8s.DashboardSvcProto, - service.Name, - BootstrapConf.K8s.ReleaseNamespace, - service.Spec.Ports[0].Port, - ), nil + return conf, nil } diff --git a/data/constants.go b/pkg/constants/constants.go similarity index 95% rename from data/constants.go rename to pkg/constants/constants.go index 251d83e..c335a2c 100644 --- a/data/constants.go +++ b/pkg/constants/constants.go @@ -1,4 +1,4 @@ -package data +package constants const ( TykBootstrapLabel = "tyk.tyk.io/k8s-bootstrap" diff --git a/license/license.go b/pkg/license.go similarity index 97% rename from license/license.go rename to pkg/license.go index b3c2274..d62ea86 100644 --- a/license/license.go +++ b/pkg/license.go @@ -1,4 +1,4 @@ -package license +package pkg import ( "errors" diff --git a/preinstallation/preinstallation.go b/preinstallation/preinstallation.go deleted file mode 100644 index e611057..0000000 --- a/preinstallation/preinstallation.go +++ /dev/null @@ -1,24 +0,0 @@ -// Package preinstallation exposes an API to run necessary operations required in pre-install hook job of bootstrapping. -// While bootstrapping Tyk Stack, users need to provide a valida Tyk License key. -// In the pre-hook installation, the helper functions defined in this package verifies the validity of the license. -package preinstallation - -import ( - "errors" - "tyk/tyk/bootstrap/data" - "tyk/tyk/bootstrap/license" -) - -// PreHookInstall runs all required license validation operations that are required in pre-install hook. -func PreHookInstall() error { - licenseIsValid, err := license.ValidateDashboardLicense(data.BootstrapConf.Tyk.DashboardLicense) - if err != nil { - return err - } - - if !licenseIsValid { - return errors.New("provided license is invalid") - } - - return nil -} diff --git a/tyk/api/request.go b/tyk/api/request.go new file mode 100644 index 0000000..a3a8167 --- /dev/null +++ b/tyk/api/request.go @@ -0,0 +1,57 @@ +package api + +type CreateOrgReq struct { + OwnerName string `json:"owner_name"` + CnameEnabled bool `json:"cname_enabled"` + Cname string `json:"cname"` +} + +type ResetPasswordReq struct { + NewPassword string `json:"new_password"` + UserPermissions map[string]string `json:"user_permissions"` +} + +type CreateUserReq struct { + OrganisationId string `json:"org_id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + EmailAddress string `json:"email_address"` + Active bool `json:"active"` + UserPermissions map[string]string `json:"user_permissions"` +} + +type PortalHomepageReq struct { + IsHomepage bool `json:"is_homepage"` + TemplateName string `json:"template_name"` + Title string `json:"title"` + Slug string `json:"slug"` + Fields PortalFields `json:"fields"` +} + +type PortalFields struct { + JumboCTATitle string `json:"JumboCTATitle"` + SubHeading string `json:"SubHeading"` + JumboCTALink string `json:"JumboCTALink"` + JumboCTALinkTitle string `json:"JumboCTALinkTitle"` + PanelOneContent string `json:"PanelOneContent"` + PanelOneLink string `json:"PanelOneLink"` + PanelOneLinkTitle string `json:"PanelOneLinkTitle"` + PanelOneTitle string `json:"PanelOneTitle"` + PanelThereeContent string `json:"PanelThereeContent"` + PanelThreeContent string `json:"PanelThreeContent"` + PanelThreeLink string `json:"PanelThreeLink"` + PanelThreeLinkTitle string `json:"PanelThreeLinkTitle"` + PanelThreeTitle string `json:"PanelThreeTitle"` + PanelTwoContent string `json:"PanelTwoContent"` + PanelTwoLink string `json:"PanelTwoLink"` + PanelTwoLinkTitle string `json:"PanelTwoLinkTitle"` + PanelTwoTitle string `json:"PanelTwoTitle"` +} + +type InitCatalogReq struct { + OrgId string `json:"org_id"` +} + +type CnameReq struct { + Cname string `json:"cname"` +} diff --git a/tyk/api/response.go b/tyk/api/response.go new file mode 100644 index 0000000..b359dcb --- /dev/null +++ b/tyk/api/response.go @@ -0,0 +1,52 @@ +package api + +import "time" + +type DashboardAPIResp struct { + Status string `json:"Status"` + Message string `json:"Message"` + Meta string `json:"Meta"` +} + +type OrgAPIResp struct { + Organisations []map[string]interface{} `json:"organisations"` + Pages int `json:"pages"` +} + +type GetUserResp struct { + APIModel struct{} `json:"api_model"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + EmailAddress string `json:"email_address"` + Password string `json:"password"` + OrgID string `json:"org_id"` + Active bool `json:"active"` + ID string `json:"id"` + AccessKey string `json:"access_key"` + UserPermissions map[string]string `json:"user_permissions"` +} + +type ListUsersResp []GetUserResp + +type CreateUserResp struct { + Status string `json:"Status"` + Message string `json:"Message"` + Meta struct { + APIModel struct{} `json:"api_model"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + EmailAddress string `json:"email_address"` + OrgID string `json:"org_id"` + Active bool `json:"active"` + ID string `json:"id"` + AccessKey string `json:"access_key"` + UserPermissions struct { + IsAdmin string `json:"IsAdmin"` + } `json:"user_permissions"` + GroupID string `json:"group_id"` + PasswordMaxDays int `json:"password_max_days"` + PasswordUpdated time.Time `json:"password_updated"` + PWHistory []interface{} `json:"PWHistory"` + CreatedAt time.Time `json:"created_at"` + } `json:"Meta"` +} diff --git a/tyk/internal/constants/constants.go b/tyk/internal/constants/constants.go new file mode 100644 index 0000000..7cfd72d --- /dev/null +++ b/tyk/internal/constants/constants.go @@ -0,0 +1,11 @@ +package constants + +const ( + AdminOrganisationsEndpoint = "/admin/organisations" + ApiUsers = "/api/users?p=-2" + ApiUsersActionsResetEndpoint = "%s/api/users/%s/actions/reset" + ApiPortalCatalogueEndpoint = "/api/portal/catalogue" + ApiPortalPagesEndpoint = "/api/portal/pages" + ApiPortalConfigurationEndpoint = "/api/portal/configuration" + ApiPortalCnameEndpoint = "/api/portal/cname" +) diff --git a/tyk/organisation.go b/tyk/organisation.go new file mode 100644 index 0000000..69e6eaa --- /dev/null +++ b/tyk/organisation.go @@ -0,0 +1,120 @@ +package tyk + +import ( + "bytes" + "errors" + "io" + "net/http" + constants2 "tyk/tyk/bootstrap/pkg/constants" + "tyk/tyk/bootstrap/tyk/api" + "tyk/tyk/bootstrap/tyk/internal/constants" + + "k8s.io/apimachinery/pkg/util/json" +) + +var ErrOrgExists = errors.New("there shouldn't be any organisations, please " + + "disable bootstrapping to avoid losing data or delete " + + "already existing organisations") + +func (s *Service) OrgExists() error { + //s.l.Info( + // "looking if organisation exists", + // "Org Cname", s.appArgs.Tyk.Org.Cname, + // "Org Name", s.appArgs.Tyk.Org.Name, + //) + + orgsApiEndpoint := s.appArgs.K8s.DashboardSvcUrl + constants.AdminOrganisationsEndpoint + + req, err := http.NewRequest(http.MethodGet, orgsApiEndpoint, nil) + if err != nil { + return err + } + + req.Header.Set(constants2.AdminAuthHeader, s.appArgs.Tyk.Admin.Secret) + req.Header.Set(constants2.ContentTypeHeader, "application/json") + + res, err := s.httpClient.Do(req) + if err != nil { + return err + } + + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + return err + } + + orgs := api.OrgAPIResp{} + + err = json.Unmarshal(bodyBytes, &orgs) + if err != nil { + return err + } + + if len(orgs.Organisations) > 0 { + for _, organisation := range orgs.Organisations { + if organisation["owner_name"] == s.appArgs.Tyk.Org.Name || + organisation["cname"] == s.appArgs.Tyk.Org.Cname { + //s.l.Info( + // "looking if organisation exists", + // "Org Cname", s.appArgs.Tyk.Org.Cname, + // "Org Name", s.appArgs.Tyk.Org.Name, + //) + + return ErrOrgExists + } + } + } + + return nil +} + +func (s *Service) CreateOrganisation() error { + //s.l.Info( + // "creating an organisation", + // "Name", s.appArgs.Tyk.Org.Name, + // "Cname", s.appArgs.Tyk.Org.Cname, + //) + + createOrgData := api.CreateOrgReq{ + OwnerName: s.appArgs.Tyk.Org.Name, + CnameEnabled: true, + Cname: s.appArgs.Tyk.Org.Cname, + } + + reqBodyBytes, err := json.Marshal(createOrgData) + if err != nil { + return err + } + + req, err := http.NewRequest( + http.MethodPost, + s.appArgs.K8s.DashboardSvcUrl+constants.AdminOrganisationsEndpoint, + bytes.NewReader(reqBodyBytes)) + if err != nil { + return err + } + + req.Header.Set(constants2.AdminAuthHeader, s.appArgs.Tyk.Admin.Secret) + req.Header.Set(constants2.ContentTypeHeader, "application/json") + + res, err := s.httpClient.Do(req) + if err != nil { + return err + } + + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + return err + } + + createOrgResp := api.DashboardAPIResp{} + + err = json.Unmarshal(bodyBytes, &createOrgResp) + if err != nil { + return err + } + + s.appArgs.Tyk.Org.ID = createOrgResp.Meta + + return nil +} diff --git a/tyk/portal.go b/tyk/portal.go new file mode 100644 index 0000000..bcb68b6 --- /dev/null +++ b/tyk/portal.go @@ -0,0 +1,204 @@ +package tyk + +import ( + "bytes" + "errors" + "fmt" + "io" + "k8s.io/apimachinery/pkg/util/json" + "net/http" + constants2 "tyk/tyk/bootstrap/pkg/constants" + "tyk/tyk/bootstrap/tyk/api" + "tyk/tyk/bootstrap/tyk/internal/constants" +) + +func (s *Service) BootstrapClassicPortal() error { + err := s.createPortalDefaultSettings() + if err != nil { + return err + } + + err = s.initialiseCatalogue() + if err != nil { + return err + } + + err = s.createPortalHomePage() + if err != nil { + return err + } + + err = s.setPortalCname() + if err != nil { + return err + } + + return nil +} + +func (s *Service) setPortalCname() error { + fmt.Println("Setting portal cname") + + cnameReq := api.CnameReq{Cname: s.appArgs.Tyk.Org.Cname} + + reqBody, err := json.Marshal(cnameReq) + if err != nil { + return err + } + + req, err := http.NewRequest( + http.MethodPut, + s.appArgs.K8s.DashboardSvcUrl+constants.ApiPortalCnameEndpoint, + bytes.NewReader(reqBody), + ) + if err != nil { + return err + } + + req.Header.Set(constants2.AuthorizationHeader, s.appArgs.Tyk.Admin.Auth) + + res, err := s.httpClient.Do(req) + if err != nil { + return err + } + + if res.StatusCode != http.StatusOK { + return errors.New("failed to set portal cname") + } + + // restarting the dashboard to apply the new cname + return nil +} + +func (s *Service) initialiseCatalogue() error { + fmt.Println("Initialising Catalogue") + + initCatalog := api.InitCatalogReq{OrgId: s.appArgs.Tyk.Org.ID} + + reqBody, err := json.Marshal(initCatalog) + if err != nil { + return err + } + + req, err := http.NewRequest( + http.MethodPost, + s.appArgs.K8s.DashboardSvcUrl+constants.ApiPortalCatalogueEndpoint, + bytes.NewReader(reqBody), + ) + if err != nil { + return err + } + + req.Header.Set(constants2.AuthorizationHeader, s.appArgs.Tyk.Admin.Auth) + + res, err := s.httpClient.Do(req) + if err != nil || res.StatusCode != http.StatusOK { + return err + } + + resp := api.DashboardAPIResp{} + + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + } + + err = json.Unmarshal(bodyBytes, &resp) + if err != nil { + return err + } + + return nil +} + +func (s *Service) createPortalHomePage() error { + fmt.Println("Creating portal homepage") + + homepageContents := portalHomepageReq() + + reqBody, err := json.Marshal(homepageContents) + if err != nil { + return err + } + + reqData := bytes.NewReader(reqBody) + + req, err := http.NewRequest(http.MethodPost, s.appArgs.K8s.DashboardSvcUrl+constants.ApiPortalPagesEndpoint, reqData) + if err != nil { + return err + } + + req.Header.Set(constants2.AuthorizationHeader, s.appArgs.Tyk.Admin.Auth) + + res, err := s.httpClient.Do(req) + if err != nil || res.StatusCode != http.StatusOK { + return err + } + + resp := api.DashboardAPIResp{} + + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + } + + err = json.Unmarshal(bodyBytes, &resp) + if err != nil { + return err + } + + return nil +} + +func portalHomepageReq() api.PortalHomepageReq { + return api.PortalHomepageReq{ + IsHomepage: true, + TemplateName: "", + Title: "Developer portal name", + Slug: "/", + Fields: api.PortalFields{ + JumboCTATitle: "Tyk Developer Portal", + SubHeading: "Sub Header", + JumboCTALink: "#cta", + JumboCTALinkTitle: "Your awesome APIs, hosted with Tyk!", + PanelOneContent: "Panel 1 content.", + PanelOneLink: "#panel1", + PanelOneLinkTitle: "Panel 1 Button", + PanelOneTitle: "Panel 1 Title", + PanelThereeContent: "", + PanelThreeContent: "Panel 3 content.", + PanelThreeLink: "#panel3", + PanelThreeLinkTitle: "Panel 3 Button", + PanelThreeTitle: "Panel 3 Title", + PanelTwoContent: "Panel 2 content.", + PanelTwoLink: "#panel2", + PanelTwoLinkTitle: "Panel 2 Button", + PanelTwoTitle: "Panel 2 Title", + }, + } +} + +func (s *Service) createPortalDefaultSettings() error { + fmt.Println("Creating bootstrap default settings") + + // TODO(buraksekili): DashboardSvcUrl can be populated via environment variables. So, the URL + // might have trailing slashes. Constructing the URL with raw string concatenating is not a good + // approach here. Needs refactoring. + req, err := http.NewRequest( + http.MethodPut, + s.appArgs.K8s.DashboardSvcUrl+constants.ApiPortalConfigurationEndpoint, + nil, + ) + req.Header.Set(constants2.AuthorizationHeader, s.appArgs.Tyk.Admin.Auth) + + if err != nil { + return err + } + + res, err := s.httpClient.Do(req) + if err != nil || res.StatusCode != http.StatusOK { + return err + } + + return nil +} diff --git a/tyk/tyk.go b/tyk/tyk.go new file mode 100644 index 0000000..3587340 --- /dev/null +++ b/tyk/tyk.go @@ -0,0 +1,20 @@ +package tyk + +import ( + "crypto/tls" + "net/http" + "tyk/tyk/bootstrap/pkg/config" +) + +type Service struct { + httpClient http.Client + appArgs *config.Config +} + +func NewService(args *config.Config) Service { + tp := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: args.InsecureSkipVerify}, + } + + return Service{httpClient: http.Client{Transport: tp}, appArgs: args} +} diff --git a/tyk/user.go b/tyk/user.go new file mode 100644 index 0000000..b846d42 --- /dev/null +++ b/tyk/user.go @@ -0,0 +1,159 @@ +package tyk + +import ( + "bytes" + "errors" + "fmt" + "io" + "k8s.io/apimachinery/pkg/util/json" + "net/http" + constants2 "tyk/tyk/bootstrap/pkg/constants" + "tyk/tyk/bootstrap/tyk/api" + "tyk/tyk/bootstrap/tyk/internal/constants" +) + +func (s *Service) CreateAdmin() error { + adminData, err := s.createAdmin() + if err != nil { + return err + } + + err = s.setAdminPassword(adminData) + if err != nil { + return err + } + + s.appArgs.Tyk.Admin.Auth = adminData.AuthCode + + return nil +} + +//func (s *Service) UserExists(userEmail string) (*NeededUserData, error) { +// s.l.Info("checking if users exists") +// +// req, err := http.NewRequest(http.MethodGet, s.appArgs.K8s.DashboardSvcUrl+constants.ApiUsers, nil) +// if err != nil { +// return nil, err +// } +// +// req.Header.Set(data.AuthorizationHeader, s.appArgs.Tyk.Admin.Secret) +// req.Header.Set(data.ContentTypeHeader, "application/json") +// +// res, err := s.httpClient.Do(req) +// if err != nil { +// return nil, err +// } +// +// bodyBytes, err := io.ReadAll(res.Body) +// if err != nil { +// return nil, err +// } +// +// users := api.ListUsersResp{} +// +// fmt.Printf("%#v\n", string(bodyBytes)) +// +// err = json.Unmarshal(bodyBytes, &users) +// if err != nil { +// return nil, err +// } +// +// for i := range users { +// user := users[i] +// if user.EmailAddress == userEmail { +// s.appArgs.Tyk.Admin.Auth = user.AccessKey +// s.appArgs.Tyk.Org.ID = user.OrgID +// return &NeededUserData{UserId: user.ID, AuthCode: user.AccessKey}, nil +// } +// } +// +// return nil, nil +//} + +func (s *Service) setAdminPassword(adminData NeededUserData) error { + newPasswordData := api.ResetPasswordReq{ + NewPassword: s.appArgs.Tyk.Admin.Password, + UserPermissions: map[string]string{"IsAdmin": "admin"}, + } + + reqBody, err := json.Marshal(newPasswordData) + if err != nil { + return err + } + + req, err := http.NewRequest( + http.MethodPost, + fmt.Sprintf(constants.ApiUsersActionsResetEndpoint, s.appArgs.K8s.DashboardSvcUrl, adminData.UserId), + bytes.NewReader(reqBody)) + if err != nil { + return err + } + + req.Header.Set(constants2.AuthorizationHeader, adminData.AuthCode) + req.Header.Set(constants2.ContentTypeHeader, "application/json") + + res, err := s.httpClient.Do(req) + if err != nil { + return err + } + + if res.StatusCode != 200 { + return errors.New("resetting password did not work") + } + + return nil +} + +type NeededUserData struct { + AuthCode string + UserId string +} + +var ErrUserExists = fmt.Errorf("User email already exists for this Org") + +func (s *Service) createAdmin() (NeededUserData, error) { + reqBody := api.CreateUserReq{ + OrganisationId: s.appArgs.Tyk.Org.ID, + FirstName: s.appArgs.Tyk.Admin.FirstName, + LastName: s.appArgs.Tyk.Admin.LastName, + EmailAddress: s.appArgs.Tyk.Admin.EmailAddress, + Active: true, + UserPermissions: map[string]string{"IsAdmin": "admin"}, + } + + reqBytes, err := json.Marshal(reqBody) + if err != nil { + return NeededUserData{}, err + } + + req, err := http.NewRequest(http.MethodPost, s.appArgs.K8s.DashboardSvcUrl+"/admin/users", bytes.NewReader(reqBytes)) + if err != nil { + return NeededUserData{}, err + } + + req.Header.Set(constants2.AdminAuthHeader, s.appArgs.Tyk.Admin.Secret) + req.Header.Set(constants2.ContentTypeHeader, "application/json") + + res, err := s.httpClient.Do(req) + if err != nil { + return NeededUserData{}, err + } + + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + return NeededUserData{}, err + } + + if res.StatusCode == http.StatusForbidden { + return NeededUserData{}, ErrUserExists + } + + resp := api.CreateUserResp{} + + err = json.Unmarshal(bodyBytes, &resp) + if err != nil { + return NeededUserData{}, err + } + + return NeededUserData{UserId: resp.Meta.ID, AuthCode: resp.Message}, nil +}