diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml index e0e3824..91a0cc8 100644 --- a/.github/workflows/linter.yaml +++ b/.github/workflows/linter.yaml @@ -3,14 +3,14 @@ name: golangci-lint on: push: branches: - - master - paths: + - main + paths: - '**.go' pull_request: branches: - - master - paths: + - main + paths: - '**.go' jobs: @@ -18,13 +18,16 @@ jobs: name: linter runs-on: ubuntu-latest steps: + - uses: actions/setup-go@v3 + with: + go-version: 1.19 - name: check out code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: golangci-lint - uses: golangci/golangci-lint-action@v2.5.2 + uses: golangci/golangci-lint-action@v3 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: latest + version: v1.50.1 # Optional: golangci-lint command line arguments. args: --verbose --timeout=5m diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..402b4a2 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,80 @@ +issues: + # Show only new issues created since branching away from default branch on the remote + new-from-rev: origin/master + exclude-rules: + - linters: + - lll + source: "^// " +linters: + enable: + - errcheck + - gocritic + - gofmt + - gofumpt + - goimports + - govet + - lll + - whitespace + - wsl + +linters-settings: + errcheck: + # Report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`. + check-blank: true + + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + check-type-assertions: true + + gocritic: + # See https://go-critic.github.io/overview#checks-overview + # To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run` + enabled-tags: + - performance + + gofmt: + # Simplify code with '-s' option + simplify: true + + gofumpt: + # Choose whether or not to use the extra rules that are disabled by default + extra-rules: true + + # Select the Go version to target. + lang-version: "1.19" + + lll: + # Max line length; lines longer will be reported + # '\t' is counted as 1 character by default, and can be changed with the 'tab-width' option + line-length: 120 + # Tab width in spaces + tab-width: 2 + + whitespace: + multi-if: false # Enforces newlines (or comments) after every multi-line if statement + multi-func: false # Enforces newlines (or comments) after every multi-line function signature + + wsl: + # Controls if you may cuddle assignments and anything without needing an empty line between them. + allow-assign-and-anything: false + # Allow calls and assignments to be cuddled as long as the lines have any matching variables, fields or types. + # Default is true. + allow-assign-and-call: true + # Controls if you may end case statements with a whitespace. + allow-case-trailing-whitespace: true + # Allow declarations (var) to be cuddled. + allow-cuddle-declarations: true + # Allow multiline assignments to be cuddled. Default is true. + allow-multiline-assign: true + # This option allows whitespace after each comment group that begins a block. + allow-separated-leading-comment: false + # Allow trailing comments in ending of blocks + allow-trailing-comment: false + # Enforces that an if statement checking an error variable is cuddled with the line that assigned that error variable. + enforce-err-cuddling: true + # Force newlines in end of case at this limit (0 = never). + force-case-trailing-whitespace: 0 + # Enforces that an assignment which is actually a short declaration (using :=) is only allowed to cuddle with other short declarations, and not plain assignments, blocks, etc. + force-short-decl-cuddling: false + # Append is only allowed to be cuddled if appending value is matching variables, fields or types on line above. + # Default is true. + strict-append: true diff --git a/Makefile b/Makefile index a3f6e3a..4ef0824 100644 --- a/Makefile +++ b/Makefile @@ -27,3 +27,9 @@ build-bootstrap-pre-delete: "-X main.version=$(MAIN_VERSION)" "$(BOOTSTRAP_CMD_PREDELETE_PATH)" build-all: build-bootstrap-post build-bootstrap-pre-delete build-bootstrap-pre-install + +linters: + go fmt ./... + gofmt -s -w . + go vet ./... + golangci-lint run diff --git a/README.md b/README.md index 04658b1..f9431c8 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,63 @@ -# Tyk-K8S-bootstrap +# Tyk-K8S-Bootstrap -This is a standalone app meant to help with bootstrap and deletion of the tyk-stack when installed -via tyk-helm-charts. - -**Note:** -
This app is needed only for Tyk [Self-managed](https://tyk.io/docs/tyk-on-premises/) deployment! -
[Tyk OSS](https://tyk.io/docs/apim/open-source/) doesn't have a special bootstrap and in [Tyk Cloud](https://tyk.io/docs/tyk-cloud/) it is done for you (being a SaaS). +Tyk K8s Bootstrap comes with three applications to bootstrap [`tyk-stack`](https://github.com/TykTechnologies/tyk-charts/tree/main/tyk-stack) +and to create Kubernetes secrets that can be utilized in [Tyk Operator](https://tyk.io/docs/tyk-operator/) and +[`tyk-dev-portal`](https://github.com/TykTechnologies/tyk-charts/tree/main/components/tyk-dev-portal) chart. ## What it does? -### 1. Tyk post deployment bootstrapping -a. Creates a basic organization with the values specified in the env vars -via the tyk-helm charts -
-b. Creates a user to access the dashboard (values determined as above) -
-c. Bootstraps tyk-portal with a mock page (only if enabled in tyk-helm-charts) -
-d. Creates the secret required for the tyk-operator to work (only if enabled in tyk-helm-charts) +`tyk-k8s-bootstrap` offers three applications functioning as [Chart Hooks](https://helm.sh/docs/topics/charts_hooks/) in Helm charts. + +- `bootstrap-pre-install` is a binary functioning as a `pre-install` hook, validating the Tyk Dashboard License key. +- `bootstrap-post-install` is a binary functioning as a `post-install` hook, bootstrapping the Tyk Dashboard by +setting up an organization and an admin user. Additionally, it generates Kubernetes secrets utilized by +[Tyk Operator](https://tyk.io/docs/tyk-operator/) and [Tyk Enterprise Portal](https://tyk.io/docs/tyk-stack/tyk-developer-portal/enterprise-developer-portal/install-tyk-enterprise-portal/) +- `bootstrap-pre-delete` is a binary functioning as a `pre-delete` hook, responsible for system cleanup. + +## Environment Variables + +| Environment Variable | Description | +|------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| TYK_K8SBOOTSTRAP_INSECURESKIPVERIFY | enables InsecureSkipVerify options in HTTP requests sent to Tyk -
might be useful for Tyk Dashboard with self-signed certs | +| TYK_K8SBOOTSTRAP_BOOTSTRAPDASHBOARD | controls bootstrapping Tyk Dashboard or not. | +| TYK_K8SBOOTSTRAP_BOOTSTRAPPORTAL | controls bootstrapping Tyk Classic Portal or not. | +| TYK_K8SBOOTSTRAP_OPERATORKUBERNETESSECRETNAME | corresponds to the Kubernetes secret name that will be created for Tyk Operator.
Set it to an empty to string to disable bootstrapping Kubernetes secret for Tyk Operator. | +| TYK_K8SBOOTSTRAP_DEVPORTALKUBERNETESSECRETNAME | corresponds to the Kubernetes secret name that will be created for Tyk Developer Enterprise Portal.
Set it to an empty to string to disable bootstrapping Kubernetes secret for Tyk Developer Enterprise Portal. | +| TYK_K8SBOOTSTRAP_K8S_DASHBOARDSVCURL | corresponds to the URL of Tyk Dashboard. | +| TYK_K8SBOOTSTRAP_K8S_DASHBOARDSVCPROTO | corresponds to Tyk Dashboard Service Protocol (either http or https). By default, it is http. | +| TYK_K8SBOOTSTRAP_K8S_RELEASENAMESPACE | corresponds to the namespace where Tyk is deployed via Helm Chart. | +| TYK_K8SBOOTSTRAP_K8S_DASHBOARDDEPLOYMENTNAME | corresponds to the name of the Tyk Dashboard Deployment, which is being used to restart
Dashboard pod after bootstrapping. | +| TYK_K8SBOOTSTRAP_TYK_ADMIN_SECRET | corresponds to the secret that will be used in Admin APIs. | +| TYK_K8SBOOTSTRAP_TYK_ADMIN_FIRSTNAME | corresponds to the first name of the admin being created. | +| TYK_K8SBOOTSTRAP_TYK_ADMIN_LASTNAME | corresponds to the last name of the admin being created. | +| TYK_K8SBOOTSTRAP_TYK_ADMIN_EMAILADDRESS | corresponds to the email address of the admin being created. | +| TYK_K8SBOOTSTRAP_TYK_ADMIN_PASSWORD | corresponds to the password of the admin being created. | +| TYK_K8SBOOTSTRAP_TYK_ADMIN_AUTH | corresponds to Tyk Dashboard API Access Credentials of the admin user, and it will be used in Authorization
header of the HTTP requests that will be sent to Tyk for bootstrapping. | +| TYK_K8SBOOTSTRAP_TYK_ORG_NAME | corresponds to the name for your organization that is going to be bootstrapped in Tyk. | +| TYK_K8SBOOTSTRAP_TYK_ORG_CNAME | corresponds to the Organisation CNAME which is going to bind the Portal to. | +| TYK_K8SBOOTSTRAP_TYK_ORG_ID | corresponds to the organisation ID that is being created. | +| TYK_K8SBOOTSTRAP_TYK_DASHBOARDLICENSE | corresponds to the license key of Tyk Dashboard. | +## Required RBAC roles for the app to work inside the Kubernetes cluster +Given that the applications operate as Chart Hooks to execute specific actions, such as creating Kubernetes Secrets, +validating component health statuses, and performing system cleanup during the deletion of the Helm Release, +they require specific RBAC rules for each operation. -### 2. Tyk pre deletion hook -a. Ensures that no failed jobs are still running by deleting them (as they prevented -
-b. clean uninstallation of the helm charts) -
-c. Also detects and deletes an existing tyk-operator-secret on helm charts uninstallation +The required roles can be found here: +[`bootstrap-role.yaml`](https://github.com/TykTechnologies/tyk-charts/blob/main/components/tyk-bootstrap/templates/bootstrap-role.yml) -Required RBAC roles for the app to work inside the k8s cluster: -- delete -- list +## Useful testing tips and commands: +### Load images to Kind Cluster + +After making your changes to applications, running the following command loads your local changes into KinD cluster with `tykio/tyk-k8s-boostrap{pre-post}-{delete-install}:testing` image. + +```bash +$ ./hack/load_images.sh +``` -### Useful debug/test tips/commands: +### KinD with a local image repository If you want to create a k8s kind cluster that also has a local repository where you can push the images generated by the Makefile just run the "local_registry.sh" script. diff --git a/cmd/bootstrap-post/main.go b/cmd/bootstrap-post/main.go index ef56dde..7ad30c1 100644 --- a/cmd/bootstrap-post/main.go +++ b/cmd/bootstrap-post/main.go @@ -11,7 +11,7 @@ import ( ) func main() { - err := data.InitAppDataPostInstall() + err := data.InitPostInstall() if err != nil { fmt.Println(err) os.Exit(1) @@ -24,38 +24,41 @@ func main() { } tp := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: data.AppConfig.DashboardInsecureSkipVerify}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: data.BootstrapConf.InsecureSkipVerify}, } client := http.Client{Transport: tp} fmt.Println("Started creating dashboard org") + err = helpers.CheckForExistingOrganisation(client) if err != nil { fmt.Println(err) os.Exit(1) } - fmt.Println("Finished creating dashboard org") + fmt.Println("Finished creating dashboard org") fmt.Println("Generating dashboard credentials") + err = helpers.GenerateDashboardCredentials(client) if err != nil { fmt.Println(err) os.Exit(1) } - fmt.Println("Finished generating dashboard credentials") + fmt.Println("Finished generating dashboard credentials") fmt.Println("Started bootstrapping operator secret") - if data.AppConfig.OperatorSecretEnabled { + + if data.BootstrapConf.OperatorKubernetesSecretName != "" { err = helpers.BootstrapTykOperatorSecret() if err != nil { fmt.Println(err) os.Exit(1) } } - fmt.Println("Finished bootstrapping operator secret") - fmt.Println("Started bootstrapping portal secret") - if data.AppConfig.DeveloperPortalSecretEnabled { + fmt.Println("Finished bootstrapping operator secret\nStarted bootstrapping portal secret") + + if data.BootstrapConf.DevPortalKubernetesSecretName != "" { err = helpers.BootstrapTykPortalSecret() if err != nil { fmt.Println(err) @@ -64,13 +67,14 @@ func main() { } fmt.Println("Started bootstrapping portal with requests to dashboard") - if data.AppConfig.BootstrapPortal { + + if data.BootstrapConf.BootstrapPortal { err = helpers.BoostrapPortal(client) if err != nil { fmt.Println(err) os.Exit(1) } } - fmt.Println("Finished bootstrapping portal") + fmt.Println("Finished bootstrapping portal") } diff --git a/cmd/bootstrap-pre-delete/main.go b/cmd/bootstrap-pre-delete/main.go index ce461e2..bde3d9c 100644 --- a/cmd/bootstrap-pre-delete/main.go +++ b/cmd/bootstrap-pre-delete/main.go @@ -8,7 +8,7 @@ import ( ) func main() { - err := data.InitAppDataPreDelete() + err := data.InitBootstrapConf() 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 093a69b..85d1b06 100644 --- a/cmd/bootstrap-pre-install/main.go +++ b/cmd/bootstrap-pre-install/main.go @@ -3,11 +3,18 @@ package main import ( "fmt" "os" + "tyk/tyk/bootstrap/data" "tyk/tyk/bootstrap/preinstallation" ) func main() { - err := preinstallation.PreHookInstall() + err := data.InitBootstrapConf() + if err != nil { + fmt.Printf("Failed to parse bootstrap environment variables, err: %v", err) + os.Exit(1) + } + + err = preinstallation.PreHookInstall() if err != nil { fmt.Printf("Failed to run pre-hook job, err: %v", err) os.Exit(1) diff --git a/constants/constants.go b/constants/constants.go deleted file mode 100644 index e603544..0000000 --- a/constants/constants.go +++ /dev/null @@ -1,28 +0,0 @@ -package constants - -const ( - OperatorSecretEnabledEnvVar = "OPERATOR_SECRET_ENABLED" - DeveloperPortalSecretEnabledEnvVar = "DEVELOPER_PORTAL_SECRET_ENABLED" - BootstrapPortalEnvVar = "BOOTSTRAP_PORTAL" - TykDashboardDeployEnvVar = "TYK_DASHBOARD_DEPLOY" - OperatorSecretNameEnvVar = "OPERATOR_SECRET_NAME" - DeveloperPortalSecretNameEnvVar = "DEVELOPER_PORTAL_SECRET_NAME" - TykAdminFirstNameEnvVar = "TYK_ADMIN_FIRST_NAME" - TykAdminLastNameEnvVar = "TYK_ADMIN_LAST_NAME" - TykAdminEmailEnvVar = "TYK_ADMIN_EMAIL" - TykAdminPasswordEnvVar = "TYK_ADMIN_PASSWORD" - TykPodNamespaceEnvVar = "TYK_POD_NAMESPACE" - TykDashboardProtoEnvVar = "TYK_DASHBOARD_PROTO" - TykDashboardInsecureSkipVerify = "TYK_DASHBOARD_INSECURE_SKIP_VERIFY" - TykDashboardLicenseEnvVarName = "TYK_DB_LICENSEKEY" - TykDbLicensekeyEnvVar = "TYK_DB_LICENSEKEY" - TykAdminSecretEnvVar = "TYK_ADMIN_SECRET" - DashboardEnabledEnvVar = "DASHBOARD_ENABLED" - TykOrgNameEnvVar = "TYK_ORG_NAME" - TykOrgCnameEnvVar = "TYK_ORG_CNAME" - - TykBootstrapLabel = "tyk.tyk.io/k8s-bootstrap" - TykBootstrapPreDeleteLabel = "tyk-k8s-bootstrap-pre-delete" - TykBootstrapDashboardDeployLabel = "tyk-dashboard" - TykBootstrapDashboardSvcLabel = "tyk-dashboard" -) diff --git a/data/app.go b/data/app.go deleted file mode 100644 index 6f50581..0000000 --- a/data/app.go +++ /dev/null @@ -1,183 +0,0 @@ -package data - -import ( - "context" - "fmt" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "os" - "strconv" - "tyk/tyk/bootstrap/constants" -) - -type AppArguments struct { - DashboardHost string - DashboardPort int32 - DashBoardLicense string - TykAdminSecret string - CurrentOrgName string - TykAdminPassword string - Cname string - TykAdminFirstName string - TykAdminLastName string - TykAdminEmailAddress string - UserAuth string - OrgId string - CatalogId string - DashboardUrl string - DashboardProto string - TykPodNamespace string - DashboardSvc string - DashboardInsecureSkipVerify bool - IsDashboardEnabled bool - OperatorSecretEnabled bool - OperatorSecretName string - DeveloperPortalSecretEnabled bool - DeveloperPortalSecretName string - BootstrapPortal bool - DashboardDeploymentName string -} - -var AppConfig = AppArguments{ - DashboardPort: 3000, - TykAdminSecret: "12345", - CurrentOrgName: "TYKTYK", - Cname: "tykCName", - TykAdminPassword: "123456", - TykAdminFirstName: "firstName", - TykAdminEmailAddress: "tyk@tyk.io", - TykAdminLastName: "lastName", -} - -func InitAppDataPreDelete() error { - AppConfig.OperatorSecretName = os.Getenv(constants.OperatorSecretNameEnvVar) - AppConfig.DeveloperPortalSecretName = os.Getenv(constants.DeveloperPortalSecretNameEnvVar) - AppConfig.TykPodNamespace = os.Getenv(constants.TykPodNamespaceEnvVar) - return nil -} - -func InitAppDataPostInstall() error { - AppConfig.TykAdminFirstName = os.Getenv(constants.TykAdminFirstNameEnvVar) - AppConfig.TykAdminLastName = os.Getenv(constants.TykAdminLastNameEnvVar) - AppConfig.TykAdminEmailAddress = os.Getenv(constants.TykAdminEmailEnvVar) - AppConfig.TykAdminPassword = os.Getenv(constants.TykAdminPasswordEnvVar) - AppConfig.TykPodNamespace = os.Getenv(constants.TykPodNamespaceEnvVar) - AppConfig.DashboardProto = os.Getenv(constants.TykDashboardProtoEnvVar) - - AppConfig.DashBoardLicense = os.Getenv(constants.TykDbLicensekeyEnvVar) - AppConfig.TykAdminSecret = os.Getenv(constants.TykAdminSecretEnvVar) - AppConfig.CurrentOrgName = os.Getenv(constants.TykOrgNameEnvVar) - AppConfig.Cname = os.Getenv(constants.TykOrgCnameEnvVar) - - var err error - - dashEnabledRaw := os.Getenv(constants.DashboardEnabledEnvVar) - if dashEnabledRaw != "" { - AppConfig.IsDashboardEnabled, err = strconv.ParseBool(os.Getenv(constants.DashboardEnabledEnvVar)) - if err != nil { - return fmt.Errorf("failed to parse %v, err: %v", constants.DashboardEnabledEnvVar, err) - } - } - - if AppConfig.IsDashboardEnabled { - if err := discoverDashboardSvc(); err != nil { - return err - } - AppConfig.DashboardUrl = fmt.Sprintf("%s://%s.%s.svc.cluster.local:%d", - AppConfig.DashboardProto, - AppConfig.DashboardSvc, - AppConfig.TykPodNamespace, - AppConfig.DashboardPort, - ) - } - - operatorSecretEnabledRaw := os.Getenv(constants.OperatorSecretEnabledEnvVar) - if operatorSecretEnabledRaw != "" { - AppConfig.OperatorSecretEnabled, err = strconv.ParseBool(operatorSecretEnabledRaw) - if err != nil { - return fmt.Errorf("failed to parse %v, err: %v", constants.OperatorSecretEnabledEnvVar, err) - } - } - - AppConfig.OperatorSecretName = os.Getenv(constants.OperatorSecretNameEnvVar) - - developerPortalSecretEnabledRaw := os.Getenv(constants.DeveloperPortalSecretEnabledEnvVar) - if developerPortalSecretEnabledRaw != "" { - AppConfig.DeveloperPortalSecretEnabled, err = strconv.ParseBool(developerPortalSecretEnabledRaw) - if err != nil { - return err - } - } - AppConfig.DeveloperPortalSecretName = os.Getenv(constants.DeveloperPortalSecretNameEnvVar) - - bootstrapPortalBoolRaw := os.Getenv(constants.BootstrapPortalEnvVar) - if bootstrapPortalBoolRaw != "" { - AppConfig.BootstrapPortal, err = strconv.ParseBool(bootstrapPortalBoolRaw) - if err != nil { - return fmt.Errorf("failed to parse %v, err: %v", constants.BootstrapPortalEnvVar, err) - } - } - AppConfig.DashboardDeploymentName = os.Getenv(constants.TykDashboardDeployEnvVar) - - dashboardInsecureSkipVerifyRaw := os.Getenv(constants.TykDashboardInsecureSkipVerify) - if dashboardInsecureSkipVerifyRaw != "" { - AppConfig.DashboardInsecureSkipVerify, err = strconv.ParseBool(dashboardInsecureSkipVerifyRaw) - if err != nil { - return fmt.Errorf("failed to parse %v, err: %v", constants.TykDashboardInsecureSkipVerify, err) - } - } - - return nil -} - -// discoverDashboardSvc lists Service objects with constants.TykBootstrapReleaseLabel label that has -// constants.TykBootstrapDashboardSvcLabel value and gets this Service's metadata name, and port and -// updates DashboardSvc and DashboardPort fields. -func discoverDashboardSvc() 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{ - constants.TykBootstrapLabel: constants.TykBootstrapDashboardSvcLabel, - }} - - l := labels.Set(ls.MatchLabels).String() - - services, err := c. - CoreV1(). - Services(AppConfig.TykPodNamespace). - 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) - } - - AppConfig.DashboardPort = service.Spec.Ports[0].Port - AppConfig.DashboardSvc = service.Name - - return nil -} diff --git a/data/config.go b/data/config.go new file mode 100644 index 0000000..0a8e43d --- /dev/null +++ b/data/config.go @@ -0,0 +1,165 @@ +package data + +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" + +type Config struct { + // InsecureSkipVerify enables InsecureSkipVerify options in http request sent to Tyk - might be useful + // for Tyk Dashboard with self-signed certs. + InsecureSkipVerify bool + // BootstrapDashboard controls bootstrapping Tyk Dashboard or not. + BootstrapDashboard bool + // BootstrapPortal controls bootstrapping Tyk Classic Portal or not. + BootstrapPortal bool + + // OperatorKubernetesSecretName corresponds to the Kubernetes secret name that will be created for Tyk Operator. + // Set it to an empty string to disable bootstrapping Kubernetes secret for Tyk Operator. + OperatorKubernetesSecretName string + // DevPortalKubernetesSecretName corresponds to the Kubernetes secret name that will be created for + // Tyk Developer Portal. Set it to an empty to string to disable bootstrapping Kubernetes + // secret for Tyk Developer Portal. + DevPortalKubernetesSecretName string + // K8s consists of configurations for Kubernetes services of Tyk. + K8s K8sConf + // Tyk consists of configurations for Tyk components such as Tyk Dashboard Admin information + // or Tyk Portal configurations. + Tyk TykConf +} + +type K8sConf struct { + // DashboardSvcUrl corresponds to the URL of Tyk Dashboard. + DashboardSvcUrl string + // DashboardSvcProto corresponds to Tyk Dashboard Service Protocol (either http or https). + DashboardSvcProto string + // ReleaseNamespace corresponds to the namespace where Tyk is deployed via Helm Chart. + ReleaseNamespace string + // DashboardDeploymentName corresponds to the name of the Tyk Dashboard Deployment, which is being used + // to restart Dashboard pod after bootstrapping. By default, it discovers Dashboard Deployment name. + // If the environment variable is populated, the discovery will not be triggered. + DashboardDeploymentName string +} + +type TykAdmin struct { + // Secret corresponds to the secret that will be used in Admin APIs. + Secret string + // FirstName corresponds to the first name of the admin being created. + FirstName string + // LastName corresponds to the last name of the admin being created. + LastName string + // EmailAddress corresponds to the email address of the admin being created. + EmailAddress string + // Password corresponds to the password of the admin being created. + Password string + + // Auth corresponds to Tyk Dashboard API Access Credentials of the admin user, and it will be used + // 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"` +} + +type TykOrg struct { + // Name corresponds to the name for your organization that is going to be bootstrapped in Tyk + Name string + // Cname corresponds to the Organisation CNAME which is going to bind the Portal to. + Cname string + + // ID corresponds to the organisation ID that is being created. + ID string `ignored:"true"` +} + +type TykConf struct { + // Admin consists of configurations for Tyk Dashboard Admin. + Admin TykAdmin + // Org consists of configurations for the organisation that is going to be created in Tyk Dashboard. + Org TykOrg + + // DashboardLicense corresponds to the license key of Tyk Dashboard. + 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) + } + + return fmt.Sprintf("%s://%s.%s.svc.cluster.local:%d", + BootstrapConf.K8s.DashboardSvcProto, + service.Name, + BootstrapConf.K8s.ReleaseNamespace, + service.Spec.Ports[0].Port, + ), nil +} diff --git a/data/constants.go b/data/constants.go new file mode 100644 index 0000000..251d83e --- /dev/null +++ b/data/constants.go @@ -0,0 +1,12 @@ +package data + +const ( + TykBootstrapLabel = "tyk.tyk.io/k8s-bootstrap" + TykBootstrapPreDeleteLabel = "tyk-k8s-bootstrap-pre-delete" + TykBootstrapDashboardDeployLabel = "tyk-dashboard" + TykBootstrapDashboardSvcLabel = "tyk-dashboard" + + AuthorizationHeader = "Authorization" + ContentTypeHeader = "Content-Type" + AdminAuthHeader = "admin-auth" +) diff --git a/go.mod b/go.mod index 6f4ef36..bb5c161 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/google/gofuzz v1.1.0 // 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 diff --git a/go.sum b/go.sum index cfdecee..f928729 100644 --- a/go.sum +++ b/go.sum @@ -190,6 +190,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= diff --git a/helpers/operator.go b/helpers/operator.go index 3815f9a..a539663 100644 --- a/helpers/operator.go +++ b/helpers/operator.go @@ -3,11 +3,12 @@ package helpers import ( "context" "fmt" - v12 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1" + "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" - "tyk/tyk/bootstrap/data" ) func BootstrapTykOperatorSecret() error { @@ -21,20 +22,28 @@ func BootstrapTykOperatorSecret() error { return err } - secrets, err := clientset.CoreV1().Secrets(data.AppConfig.TykPodNamespace). - List(context.TODO(), v1.ListOptions{}) + secrets, err := clientset. + CoreV1(). + Secrets(data.BootstrapConf.K8s.ReleaseNamespace). + List(context.TODO(), metav1.ListOptions{}) if err != nil { return err } - for _, value := range secrets.Items { - if value.Name == data.AppConfig.OperatorSecretName { - err = clientset.CoreV1().Secrets(data.AppConfig.TykPodNamespace). - Delete(context.TODO(), value.Name, v1.DeleteOptions{}) + 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 } } @@ -49,20 +58,23 @@ func BootstrapTykOperatorSecret() error { func CreateTykOperatorSecret(clientset *kubernetes.Clientset) error { secretData := map[string][]byte{ - TykAuth: []byte(data.AppConfig.UserAuth), - TykOrg: []byte(data.AppConfig.OrgId), + TykAuth: []byte(data.BootstrapConf.Tyk.Admin.Auth), + TykOrg: []byte(data.BootstrapConf.Tyk.Org.ID), TykMode: []byte(TykModePro), - TykUrl: []byte(data.AppConfig.DashboardUrl), + TykUrl: []byte(data.BootstrapConf.K8s.DashboardSvcUrl), } - objectMeta := v1.ObjectMeta{Name: data.AppConfig.OperatorSecretName} + objectMeta := metav1.ObjectMeta{Name: data.BootstrapConf.OperatorKubernetesSecretName} - secret := v12.Secret{ + secret := v1.Secret{ ObjectMeta: objectMeta, Data: secretData, } - _, err := clientset.CoreV1().Secrets(data.AppConfig.TykPodNamespace). - Create(context.TODO(), &secret, v1.CreateOptions{}) + + _, err := clientset. + CoreV1(). + Secrets(data.BootstrapConf.K8s.ReleaseNamespace). + Create(context.TODO(), &secret, metav1.CreateOptions{}) if err != nil { return err } @@ -81,47 +93,55 @@ func BootstrapTykPortalSecret() error { return err } - secrets, err := clientset.CoreV1().Secrets(data.AppConfig.TykPodNamespace). - List(context.TODO(), v1.ListOptions{}) + secrets, err := clientset.CoreV1().Secrets(data.BootstrapConf.K8s.ReleaseNamespace). + List(context.TODO(), metav1.ListOptions{}) if err != nil { return err } - for _, value := range secrets.Items { - if data.AppConfig.DeveloperPortalSecretName == value.Name { - err = clientset.CoreV1().Secrets(data.AppConfig.TykPodNamespace). - Delete(context.TODO(), value.Name, v1.DeleteOptions{}) + 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.AppConfig.DeveloperPortalSecretName != "" { - err = CreateTykPortalSecret(clientset, data.AppConfig.DeveloperPortalSecretName) + 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.AppConfig.UserAuth), - TykOrg: []byte(data.AppConfig.OrgId), + TykAuth: []byte(data.BootstrapConf.Tyk.Admin.Auth), + TykOrg: []byte(data.BootstrapConf.Tyk.Org.ID), } - objectMeta := v1.ObjectMeta{Name: secretName} + objectMeta := metav1.ObjectMeta{Name: secretName} - secret := v12.Secret{ + secret := v1.Secret{ ObjectMeta: objectMeta, Data: secretData, } - _, err := clientset.CoreV1().Secrets(data.AppConfig.TykPodNamespace). - Create(context.TODO(), &secret, v1.CreateOptions{}) + + _, err := clientset. + CoreV1(). + Secrets(data.BootstrapConf.K8s.ReleaseNamespace). + Create(context.TODO(), &secret, metav1.CreateOptions{}) if err != nil { return err } diff --git a/helpers/organisation.go b/helpers/organisation.go index cf40472..54e29df 100644 --- a/helpers/organisation.go +++ b/helpers/organisation.go @@ -29,14 +29,16 @@ const ( func CheckForExistingOrganisation(client http.Client) error { fmt.Println("Checking for existing organisations") - orgsApiEndpoint := data.AppConfig.DashboardUrl + AdminOrganisationsEndpoint - req, err := http.NewRequest("GET", orgsApiEndpoint, nil) + orgsApiEndpoint := data.BootstrapConf.K8s.DashboardSvcUrl + AdminOrganisationsEndpoint + + req, err := http.NewRequest(http.MethodGet, orgsApiEndpoint, nil) if err != nil { return err } - req.Header.Set("admin-auth", data.AppConfig.TykAdminSecret) - req.Header.Set("Content-Type", "application/json") + 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 @@ -48,14 +50,16 @@ func CheckForExistingOrganisation(client http.Client) error { } 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.AppConfig.CurrentOrgName || - organisation["cname"] == data.AppConfig.Cname { + 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") @@ -65,6 +69,7 @@ func CheckForExistingOrganisation(client http.Client) error { fmt.Println("No organisations have been detected, we can proceed") return nil } + return nil } @@ -76,21 +81,24 @@ type CreateOrgStruct struct { func CreateOrganisation(client http.Client, dashBoardUrl string) (string, error) { createOrgData := CreateOrgStruct{ - OwnerName: data.AppConfig.CurrentOrgName, + OwnerName: data.BootstrapConf.Tyk.Org.Name, CnameEnabled: true, - Cname: data.AppConfig.Cname, + Cname: data.BootstrapConf.Tyk.Org.Cname, } + reqBodyBytes, err := json.Marshal(createOrgData) if err != nil { return "", err } - reqBody := bytes.NewReader(reqBodyBytes) - req, err := http.NewRequest("POST", dashBoardUrl+AdminOrganisationsEndpoint, reqBody) + + req, err := http.NewRequest(http.MethodPost, dashBoardUrl+AdminOrganisationsEndpoint, bytes.NewReader(reqBodyBytes)) if err != nil { return "", err } - req.Header.Set("admin-auth", data.AppConfig.TykAdminSecret) - req.Header.Set("Content-Type", "application/json") + + 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 @@ -102,6 +110,7 @@ func CreateOrganisation(client http.Client, dashBoardUrl string) (string, error) } createOrgResponse := DashboardGeneralResponse{} + err = json.Unmarshal(bodyBytes, &createOrgResponse) if err != nil { return "", err diff --git a/helpers/portal.go b/helpers/portal.go index 3dda944..7006496 100644 --- a/helpers/portal.go +++ b/helpers/portal.go @@ -6,6 +6,9 @@ import ( "errors" "fmt" "io" + "net/http" + "time" + "tyk/tyk/bootstrap/data" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -13,10 +16,6 @@ import ( "k8s.io/apimachinery/pkg/util/json" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - "net/http" - "time" - "tyk/tyk/bootstrap/constants" - "tyk/tyk/bootstrap/data" ) func BoostrapPortal(client http.Client) error { @@ -54,19 +53,23 @@ type CnameRequest struct { func SetPortalCname(client http.Client) error { fmt.Println("Setting portal cname") - cnameReq := CnameRequest{Cname: data.AppConfig.Cname} + cnameReq := CnameRequest{Cname: data.BootstrapConf.Tyk.Org.Cname} + reqBody, err := json.Marshal(cnameReq) if err != nil { return err } - reqData := bytes.NewReader(reqBody) - req, err := http.NewRequest("PUT", data.AppConfig.DashboardUrl+ApiPortalCnameEndpoint, reqData) + req, err := http.NewRequest( + http.MethodPut, + data.BootstrapConf.K8s.DashboardSvcUrl+ApiPortalCnameEndpoint, + bytes.NewReader(reqBody), + ) if err != nil { return err } - req.Header.Set("Authorization", data.AppConfig.UserAuth) + req.Header.Set(data.AuthorizationHeader, data.BootstrapConf.Tyk.Admin.Auth) res, err := client.Do(req) if err != nil { @@ -84,18 +87,23 @@ func SetPortalCname(client http.Client) error { func InitialiseCatalogue(client http.Client) error { fmt.Println("Initialising Catalogue") - initCatalog := InitCatalogReq{OrgId: data.AppConfig.OrgId} + initCatalog := InitCatalogReq{OrgId: data.BootstrapConf.Tyk.Org.ID} + reqBody, err := json.Marshal(initCatalog) if err != nil { return err } - reqData := bytes.NewReader(reqBody) - req, err := http.NewRequest("POST", data.AppConfig.DashboardUrl+ApiPortalCatalogueEndpoint, reqData) + + req, err := http.NewRequest( + http.MethodPost, + data.BootstrapConf.K8s.DashboardSvcUrl+ApiPortalCatalogueEndpoint, + bytes.NewReader(reqBody), + ) if err != nil { return err } - req.Header.Set("Authorization", data.AppConfig.UserAuth) + req.Header.Set(data.AuthorizationHeader, data.BootstrapConf.Tyk.Admin.Auth) res, err := client.Do(req) if err != nil || res.StatusCode != http.StatusOK { @@ -114,8 +122,6 @@ func InitialiseCatalogue(client http.Client) error { return err } - data.AppConfig.CatalogId = resp.Message - return nil } @@ -123,21 +129,28 @@ 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("POST", data.AppConfig.DashboardUrl+ApiPortalPagesEndpoint, reqData) - req.Header.Set("Authorization", data.AppConfig.UserAuth) + + 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) @@ -177,7 +190,6 @@ func GetPortalHomepage() PortalHomepageRequest { PanelTwoTitle: "Panel 2 Title", }, } - } type PortalHomepageRequest struct { @@ -211,8 +223,15 @@ type PortalFields struct { func CreatePortalDefaultSettings(client http.Client) error { fmt.Println("Creating bootstrap default settings") - req, err := http.NewRequest("POST", data.AppConfig.DashboardUrl+ApiPortalConfigurationEndpoint, nil) - req.Header.Set("Authorization", data.AppConfig.UserAuth) + // 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 @@ -237,14 +256,14 @@ func RestartDashboard() error { return err } - if data.AppConfig.DashboardDeploymentName == "" { + if data.BootstrapConf.K8s.DashboardDeploymentName == "" { ls := metav1.LabelSelector{MatchLabels: map[string]string{ - constants.TykBootstrapLabel: constants.TykBootstrapDashboardDeployLabel, + data.TykBootstrapLabel: data.TykBootstrapDashboardDeployLabel, }} deployments, err := clientset. AppsV1(). - Deployments(data.AppConfig.TykPodNamespace). + Deployments(data.BootstrapConf.K8s.ReleaseNamespace). List( context.TODO(), metav1.ListOptions{ @@ -252,26 +271,29 @@ func RestartDashboard() error { }, ) if err != nil { - return errors.New(fmt.Sprintf("failed to list Tyk Dashboard Deployment, err: %v", err)) + return fmt.Errorf("failed to list Tyk Dashboard Deployment, err: %v", err) } - for _, deployment := range deployments.Items { - data.AppConfig.DashboardDeploymentName = deployment.ObjectMeta.Name + 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")) + timeStamp := fmt.Sprintf( + `{"spec": {"template": {"metadata": {"annotations": {"kubectl.kubernetes.io/restartedAt": "%s"}}}}}`, + time.Now().Format("20060102150405"), + ) _, err = clientset. AppsV1(). - Deployments(data.AppConfig.TykPodNamespace). + Deployments(data.BootstrapConf.K8s.ReleaseNamespace). Patch( context.TODO(), - data.AppConfig.DashboardDeploymentName, + data.BootstrapConf.K8s.DashboardDeploymentName, types.StrategicMergePatchType, []byte(timeStamp), metav1.PatchOptions{}, ) + return err } diff --git a/helpers/user.go b/helpers/user.go index f70c664..354693c 100644 --- a/helpers/user.go +++ b/helpers/user.go @@ -12,9 +12,8 @@ import ( "k8s.io/apimachinery/pkg/util/json" ) -func CreateUser(client http.Client, dashboardUrl string, orgId string) (string, error) { +func CreateUser(client http.Client, dashboardUrl, orgId string) (string, error) { userData, err := GetUserData(client, dashboardUrl, orgId) - if err != nil { return "", err } @@ -23,6 +22,7 @@ func CreateUser(client http.Client, dashboardUrl string, orgId string) (string, if err != nil { return "", err } + return userData.AuthCode, nil } @@ -31,46 +31,54 @@ type ResetPasswordStruct struct { UserPermissions map[string]string `json:"user_permissions"` } -func SetUserPassword(client http.Client, userId string, authCode string, dashboardUrl string) error { +func SetUserPassword(client http.Client, userId, authCode, dashboardUrl string) error { newPasswordData := ResetPasswordStruct{ - NewPassword: data.AppConfig.TykAdminPassword, + NewPassword: data.BootstrapConf.Tyk.Admin.Password, UserPermissions: map[string]string{"IsAdmin": "admin"}, } + reqBody, err := json.Marshal(newPasswordData) if err != nil { return err } - req, _ := http.NewRequest( - "POST", + req, err := http.NewRequest( + http.MethodPost, fmt.Sprintf(ApiUsersActionsResetEndpoint, dashboardUrl, userId), bytes.NewReader(reqBody)) - req.Header.Set("authorization", authCode) - req.Header.Set("Content-Type", "application/json") + 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.AppConfig.DashboardUrl) + orgId, err := CreateOrganisation(client, data.BootstrapConf.K8s.DashboardSvcUrl) if err != nil { return err } - data.AppConfig.OrgId = orgId + data.BootstrapConf.Tyk.Org.ID = orgId - userAuth, err := CreateUser(client, data.AppConfig.DashboardUrl, orgId) + userAuth, err := CreateUser(client, data.BootstrapConf.K8s.DashboardSvcUrl, orgId) if err != nil { return err } - data.AppConfig.UserAuth = userAuth + data.BootstrapConf.Tyk.Admin.Auth = userAuth return nil } @@ -88,15 +96,14 @@ 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"` + 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"` @@ -113,27 +120,29 @@ type NeededUserData struct { UserId string } -func GetUserData(client http.Client, dashboardUrl string, orgId string) (NeededUserData, error) { +func GetUserData(client http.Client, dashboardUrl, orgId string) (NeededUserData, error) { reqBody := CreateUserRequest{ OrganisationId: orgId, - FirstName: data.AppConfig.TykAdminFirstName, - LastName: data.AppConfig.TykAdminLastName, - EmailAddress: data.AppConfig.TykAdminEmailAddress, + 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 } - reqData := bytes.NewReader(reqBytes) - req, err := http.NewRequest("POST", dashboardUrl+"/admin/users", reqData) + + req, err := http.NewRequest(http.MethodPost, dashboardUrl+"/admin/users", bytes.NewReader(reqBytes)) if err != nil { return NeededUserData{}, err } - req.Header.Set("admin-auth", data.AppConfig.TykAdminSecret) - req.Header.Set("Content-Type", "application/json") + 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 @@ -145,6 +154,7 @@ func GetUserData(client http.Client, dashboardUrl string, orgId string) (NeededU } getUserResponse := CreateUserResponse{} + err = json.Unmarshal(bodyBytes, &getUserResponse) if err != nil { return NeededUserData{}, err diff --git a/license/license.go b/license/license.go index b47fbc1..b3c2274 100644 --- a/license/license.go +++ b/license/license.go @@ -3,38 +3,26 @@ package license import ( "errors" "fmt" - "github.com/golang-jwt/jwt" - "os" "strconv" "strings" "time" - "tyk/tyk/bootstrap/constants" -) - -func GetDashboardLicense() (string, error) { - license, ok := os.LookupEnv(constants.TykDashboardLicenseEnvVarName) - if !ok { - return "", errors.New("license env var is not present") - } - - if license == "" { - return "", errors.New("empty dashboard license") - } - return license, nil -} + "github.com/golang-jwt/jwt" +) func ValidateDashboardLicense(license string) (bool, error) { - token, _ := jwt.Parse(license, func(token *jwt.Token) (interface{}, error) { + token, _ := jwt.Parse(license, func(token *jwt.Token) (interface{}, error) { // nolint:errcheck return []byte(""), nil }) if strings.ToLower(fmt.Sprint(token.Header["typ"])) == "jwt" { exp := strings.Split(fmt.Sprintf("%f", token.Claims.(jwt.MapClaims)["exp"]), ".")[0] + expDate, err := strconv.ParseInt(exp, 10, 64) if err != nil { return false, errors.New("impossible to parse expiration date") } + if time.Unix(expDate, 0).Before(time.Now()) { return false, errors.New("expired dashboard license") } diff --git a/predelete/predelete.go b/predelete/predelete.go index 7fc9c08..2ac7e47 100644 --- a/predelete/predelete.go +++ b/predelete/predelete.go @@ -3,8 +3,6 @@ package predelete import ( "context" "fmt" - "os" - "tyk/tyk/bootstrap/constants" "tyk/tyk/bootstrap/data" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -44,19 +42,31 @@ func ExecutePreDeleteOperations() error { func PreDeleteOperatorSecret(clientset *kubernetes.Clientset) error { fmt.Println("Running pre delete hook") - secrets, err := clientset.CoreV1().Secrets(os.Getenv("TYK_POD_NAMESPACE")).List(context.TODO(), metav1.ListOptions{}) + + secrets, err := clientset. + CoreV1(). + Secrets(data.BootstrapConf.K8s.ReleaseNamespace). + List(context.TODO(), metav1.ListOptions{}) if err != nil { return err } found := false - for _, value := range secrets.Items { - if value.Name == os.Getenv("OPERATOR_SECRET_NAME") { - err = clientset.CoreV1().Secrets(os.Getenv("TYK_POD_NAMESPACE")).Delete(context.TODO(), value.Name, metav1.DeleteOptions{}) + + for i := range secrets.Items { + value := secrets.Items[i] + if value.Name == data.BootstrapConf.OperatorKubernetesSecretName { + err = clientset. + CoreV1(). + Secrets(data.BootstrapConf.K8s.ReleaseNamespace). + Delete(context.TODO(), value.Name, metav1.DeleteOptions{}) + if err != nil { return err } + found = true + break } } @@ -66,30 +76,35 @@ func PreDeleteOperatorSecret(clientset *kubernetes.Clientset) error { } else { fmt.Println("A previously created operator secret was identified and deleted") } + return nil } func PreDeletePortalSecret(clientset *kubernetes.Clientset) error { fmt.Println("Running pre delete hook") - ns := data.AppConfig.TykPodNamespace - secrets, err := clientset.CoreV1().Secrets(ns). + secrets, err := clientset.CoreV1().Secrets(data.BootstrapConf.K8s.ReleaseNamespace). List(context.TODO(), metav1.ListOptions{}) if err != nil { return err } notFound := true - for _, value := range secrets.Items { - if data.AppConfig.DeveloperPortalSecretName == value.Name { - err = clientset.CoreV1().Secrets(ns). + + for i := range secrets.Items { + value := secrets.Items[i] + if data.BootstrapConf.DevPortalKubernetesSecretName == value.Name { + err = clientset.CoreV1().Secrets(data.BootstrapConf.K8s.ReleaseNamespace). Delete(context.TODO(), value.Name, metav1.DeleteOptions{}) if err != nil { return err } + fmt.Println("A previously created developer portal secret was identified and deleted") + notFound = false + break } } @@ -106,11 +121,11 @@ func PreDeleteBootstrappingJobs(clientset *kubernetes.Clientset) error { // Usually, the raw strings in label selectors are not recommended. jobs, err := clientset. BatchV1(). - Jobs(data.AppConfig.TykPodNamespace). + Jobs(data.BootstrapConf.K8s.ReleaseNamespace). List( context.TODO(), metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s", constants.TykBootstrapLabel), + LabelSelector: data.TykBootstrapLabel, }, ) if err != nil { @@ -118,15 +133,18 @@ func PreDeleteBootstrappingJobs(clientset *kubernetes.Clientset) error { } var errCascading error - for _, job := range jobs.Items { + + for i := range jobs.Items { + job := jobs.Items[i] + // Do not need to delete pre-delete job. It will be deleted by Helm. - jobLabel := job.ObjectMeta.Labels[constants.TykBootstrapLabel] - if jobLabel != constants.TykBootstrapPreDeleteLabel { + jobLabel := job.ObjectMeta.Labels[data.TykBootstrapLabel] + if jobLabel != data.TykBootstrapPreDeleteLabel { deletePropagationType := metav1.DeletePropagationBackground err2 := clientset. BatchV1(). - Jobs(data.AppConfig.TykPodNamespace). + Jobs(data.BootstrapConf.K8s.ReleaseNamespace). Delete(context.TODO(), job.Name, metav1.DeleteOptions{PropagationPolicy: &deletePropagationType}) if err2 != nil { errCascading = err2 diff --git a/preinstallation/preinstallation.go b/preinstallation/preinstallation.go index b184063..e611057 100644 --- a/preinstallation/preinstallation.go +++ b/preinstallation/preinstallation.go @@ -5,17 +5,13 @@ 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 { - dashboardLicenseKey, err := license.GetDashboardLicense() - if err != nil { - return err - } - - licenseIsValid, err := license.ValidateDashboardLicense(dashboardLicenseKey) + licenseIsValid, err := license.ValidateDashboardLicense(data.BootstrapConf.Tyk.DashboardLicense) if err != nil { return err } diff --git a/readiness/readiness.go b/readiness/readiness.go index 2c453ac..2b9d9f1 100644 --- a/readiness/readiness.go +++ b/readiness/readiness.go @@ -4,11 +4,11 @@ import ( "context" "errors" "fmt" - v1 "k8s.io/api/core/v1" "strings" "time" "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" @@ -28,20 +28,25 @@ func CheckIfRequiredDeploymentsAreReady() error { time.Sleep(5 * time.Second) var attemptCount int + for { attemptCount++ if attemptCount > 180 { return errors.New("attempted readiness check too many times") } - pods, err := clientset.CoreV1().Pods(data.AppConfig.TykPodNamespace). + + pods, err := clientset.CoreV1().Pods(data.BootstrapConf.K8s.ReleaseNamespace). List(context.TODO(), metav1.ListOptions{}) if err != nil { return err } + fmt.Printf("There are %d other pods in the cluster\n", len(pods.Items)-1) var requiredPods []v1.Pod - for _, pod := range pods.Items { + + for i := range pods.Items { + pod := pods.Items[i] if strings.Contains(pod.Name, "dashboard") || strings.Contains(pod.Name, "redis") { requiredPods = append(requiredPods, pod) @@ -49,8 +54,11 @@ func CheckIfRequiredDeploymentsAreReady() error { } notReadyPods := make(map[string]struct{}) - for _, pod := range requiredPods { + + for i := range requiredPods { + pod := requiredPods[i] podStatus := pod.Status + for container := range pod.Spec.Containers { if !podStatus.ContainerStatuses[container].Ready { notReadyPods[pod.Name] = struct{}{} @@ -63,8 +71,9 @@ func CheckIfRequiredDeploymentsAreReady() error { } fmt.Printf("The following pods have containers that are NOT ready: ") - for pod, _ := range notReadyPods { - fmt.Println(pod) + + for podName := range notReadyPods { + fmt.Println(podName) } time.Sleep(2 * time.Second)