diff --git a/.env.example b/.env.example index ee24bc7d..9e7de448 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,20 @@ MONGODB_USERNAME=root MONGODB_PASSWORD=password -MONGODB_HOST_TYPE=local +MONGODB_HOST_TYPE=local # local | atlas MONGODB_HOST="localhost:27017" +SERVER_PORT=8081 +K1_ACCESS_TOKEN=feedkray +IS_CLUSTER_ZERO= CLUSTER_TYPE="bootstrap" CLUSTER_ID="abc456" INSTALL_METHOD="helm" -K1_ACCESS_TOKEN=feedkray \ No newline at end of file +CLOUD_PROVIDER=google +KUBEFIRST_VERSION= +DOMAIN_NAME= +GIT_PROVIDER= +KUBEFIRST_TEAM= +KUBEFIRST_TEAM_INFO= +AWS_REGION= +AWS_PROFILE= +IN_CLUSTER= +ENTERPRISE_API_URL= \ No newline at end of file diff --git a/README.md b/README.md index eb816274..a569a67f 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ Kubefirst API runtime implementation. - [local environment variables](#local-environment-variables) - [Provider Support](#provider-support) - [Creating a Cluster](#creating-a-cluster) - - [Kubernetes Secret](#kubernetes-secret) - - [API Call Parameters](#api-call-parameters) + - [Kubernetes Secret](#kubernetes-secret) + - [API Call Parameters](#api-call-parameters) - [AWS](#aws) - [Civo](#civo) - [Digital Ocean](#digital-ocean) @@ -48,7 +48,7 @@ The API is available at `http://localhost:8081/api/v1` while running. ### Build the Binary The API can be run locally for testing. It can be run by using `make build` and then calling the binary in the `bin/` directory or by using `go run .`. - + ### Leverage `air` for Live Reloading Locally **Prerequsite** - Install [air](https://github.com/cosmtrek/air). @@ -57,7 +57,7 @@ The API can be run locally for testing. It can be run by using `make build` and go install github.com/cosmtrek/air@latest ``` -Run `air` from the root of the repository. This will watch go files and live rebuild a local running instance of `kubefirst-api`. +Run `air` from the root of the repository. This will watch go files and live rebuild a local running instance of `kubefirst-api`. ## Prerequisites @@ -75,19 +75,19 @@ The host:port for MongoDB should be supplied as the environment variable `MONGOD Some variables are required, others are optional depending on deployment type. -| Variable | Description | Required | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | -| `MONGODB_HOST_TYPE` | Can be either `atlas` or `local`. | Yes | -| `MONGODB_HOST` | The host to connect to. For Atlas, use only the portion of the string not containing username or password. For all other types, append the port. | Yes | -| `MONGODB_USERNAME` | Required when using Atlas. | If using Atlas | -| `MONGODB_PASSWORD` | Required when using Atlas. | If using Atlas | -| `IN_CLUSTER` | Specify whether or not the API is running inside a Kubernetes cluster. By default, this is assumed `false`. | No | -| `CLUSTER_ID` | The ID of the cluster running API. | Yes | -| `CLUSTER_TYPE` | Cluster type. | Yes | -| `INSTALL_METHOD` | Description of the method through which the API was deployed. Example: `helm` | Yes | -| `K1_ACCESS_TOKEN` | Access token in authorization header to prevent unsolicited in-cluster access | Yes | +| Variable | Description | Required | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | +| `MONGODB_HOST_TYPE` | Can be either `atlas` or `local`. | Yes | +| `MONGODB_HOST` | The host to connect to. For Atlas, use only the portion of the string not containing username or password. For all other types, append the port. | Yes | +| `MONGODB_USERNAME` | Required when using Atlas/ Docker compose. | If using Atlas/ Docker compose | +| `MONGODB_PASSWORD` | Required when using Atlas/ Docker compose. | If using Atlas/ Docker compose | +| `IN_CLUSTER` | Specify whether or not the API is running inside a Kubernetes cluster. By default, this is assumed `false`. | No | +| `CLUSTER_ID` | The ID of the cluster running API. | Yes | +| `CLUSTER_TYPE` | Cluster type. | Yes | +| `INSTALL_METHOD` | Description of the method through which the API was deployed. Example: `helm` | Yes | +| `K1_ACCESS_TOKEN` | Access token in authorization header to prevent unsolicited in-cluster access | Yes | -### To run locally: +### To run locally: ```bash # optional local mongodb for kubefirst-api @@ -98,7 +98,14 @@ docker run -d --name k1-api-mongodb \ mongo ``` +### Using Docker compose: + +```bash + docker compose up +``` + ## local environment variables + see [this .env example](./.env.example) for the necessary values ## Provider Support @@ -146,46 +153,46 @@ This would require the following parameters added to the API call depending on w ```json { - "aws_auth": { - "access_key_id": "foo", - "secret_access_key": "bar", - "session_token": "baz" - } + "aws_auth": { + "access_key_id": "foo", + "secret_access_key": "bar", + "session_token": "baz" + } } ``` ```json { - "civo_auth": { - "token": "my-civo-token" - } + "civo_auth": { + "token": "my-civo-token" + } } ``` ```json { - "do_auth": { - "token": "my-do-token", - "spaces_key": "foo", - "spaces_secret": "bar" - } + "do_auth": { + "token": "my-do-token", + "spaces_key": "foo", + "spaces_secret": "bar" + } } ``` ```json { - "do_auth": { - "key_file": "my-google-credentials-json-keyfile-stringified-no-newline-characters", - "project_id": "google cloud project id" - } + "do_auth": { + "key_file": "my-google-credentials-json-keyfile-stringified-no-newline-characters", + "project_id": "google cloud project id" + } } ``` ```json { - "vultr_auth": { - "token": "my-vultr-api-key" - } + "vultr_auth": { + "token": "my-vultr-api-key" + } } ``` diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..9674584c --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,11 @@ +version: "3.1" + +services: + k1-api-mongodb: + image: mongo + restart: always + environment: + MONGO_INITDB_ROOT_USERNAME: ${MONGODB_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${MONGODB_PASSWORD} + ports: + - 27017:27017 diff --git a/go.mod b/go.mod index c877d2a4..c4d4ee87 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.18.19 github.com/aws/aws-sdk-go-v2/service/eks v1.27.10 github.com/aws/aws-sdk-go-v2/service/route53 v1.27.5 + github.com/caarlos0/env/v10 v10.0.0 github.com/charmbracelet/bubbles v0.15.0 github.com/charmbracelet/bubbletea v0.23.2 github.com/charmbracelet/lipgloss v0.8.0 diff --git a/go.sum b/go.sum index ab1837e5..c1bd733a 100644 --- a/go.sum +++ b/go.sum @@ -216,6 +216,8 @@ github.com/bradleyfalzon/ghinstallation/v2 v2.1.0 h1:5+NghM1Zred9Z078QEZtm28G/kf github.com/bradleyfalzon/ghinstallation/v2 v2.1.0/go.mod h1:Xg3xPRN5Mcq6GDqeUVhFbjEWMb4JHCyWEeeBGEYQoTU= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= +github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I= diff --git a/internal/aws/aws.go b/internal/aws/aws.go index 961dbe42..2c904be2 100644 --- a/internal/aws/aws.go +++ b/internal/aws/aws.go @@ -11,7 +11,6 @@ import ( "errors" "fmt" "net" - "os" "strconv" "time" @@ -19,6 +18,7 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/route53" route53Types "github.com/aws/aws-sdk-go-v2/service/route53/types" + "github.com/kubefirst/kubefirst-api/internal/env" "github.com/kubefirst/kubefirst-api/internal/utils" log "github.com/sirupsen/logrus" ) @@ -35,13 +35,12 @@ var Conf AWSConfiguration = AWSConfiguration{ // NewAws instantiates a new AWS configuration func NewAws() aws.Config { - region := os.Getenv("AWS_REGION") - profile := os.Getenv("AWS_PROFILE") - + env, _ := env.GetEnv() + awsClient, err := config.LoadDefaultConfig( context.Background(), - config.WithRegion(region), - config.WithSharedConfigProfile(profile), + config.WithRegion(env.AWSRegion), + config.WithSharedConfigProfile(env.AWSProfile), ) if err != nil { log.Errorf("Could not create AWS config: %s", err.Error()) diff --git a/internal/controller/cluster.go b/internal/controller/cluster.go index 6f47b873..2f4a1251 100644 --- a/internal/controller/cluster.go +++ b/internal/controller/cluster.go @@ -18,6 +18,7 @@ import ( googleext "github.com/kubefirst/kubefirst-api/extensions/google" terraformext "github.com/kubefirst/kubefirst-api/extensions/terraform" vultrext "github.com/kubefirst/kubefirst-api/extensions/vultr" + "github.com/kubefirst/kubefirst-api/internal/env" gitShim "github.com/kubefirst/kubefirst-api/internal/gitShim" "github.com/kubefirst/kubefirst-api/pkg/providerConfigs" pkgtypes "github.com/kubefirst/kubefirst-api/pkg/types" @@ -110,11 +111,6 @@ func (clctrl *ClusterController) CreateCluster() error { // CreateTokens func (clctrl *ClusterController) CreateTokens(kind string) interface{} { - kubefirstVersion := os.Getenv("KUBEFIRST_VERSION") - if kubefirstVersion == "" { - kubefirstVersion = "development" - } - cl, err := clctrl.MdbCl.GetCluster(clctrl.ClusterName) if err != nil { return err @@ -162,6 +158,8 @@ func (clctrl *ClusterController) CreateTokens(kind string) interface{} { return err } + env, _ := env.GetEnv() + // Default gitopsTemplateTokens gitopsTemplateTokens := &providerConfigs.GitopsDirectoryValues{ AlertsEmail: clctrl.AlertsEmail, @@ -174,7 +172,7 @@ func (clctrl *ClusterController) CreateTokens(kind string) interface{} { SubdomainName: clctrl.SubdomainName, KubefirstStateStoreBucket: clctrl.KubefirstStateStoreBucketName, KubefirstTeam: clctrl.KubefirstTeam, - KubefirstVersion: kubefirstVersion, + KubefirstVersion: env.KubefirstVersion, Kubeconfig: clctrl.ProviderConfig.Kubeconfig, //AWS KubeconfigPath: clctrl.ProviderConfig.Kubeconfig, //Not AWS @@ -251,7 +249,7 @@ func (clctrl *ClusterController) CreateTokens(kind string) interface{} { } else { // moving commented line below to default behavior // gitopsTemplateTokens.ContainerRegistryURL = fmt.Sprintf("%s/%s", clctrl.ContainerRegistryHost, clctrl.GitAuth.Owner) - log.Info("NOT using ECR but instead %s URL %s", clctrl.GitProvider, gitopsTemplateTokens.ContainerRegistryURL) + log.Infof("NOT using ECR but instead %s URL %s", clctrl.GitProvider, gitopsTemplateTokens.ContainerRegistryURL) } } @@ -374,9 +372,6 @@ func (clctrl *ClusterController) ContainerRegistryAuth() (string, error) { kcfg = awsext.CreateEKSKubeconfig(&clctrl.AwsClient.Config, clctrl.ClusterName) // Container registry authentication creation - if !clctrl.ECR { - - } containerRegistryAuth := gitShim.ContainerRegistryAuth{ GitProvider: clctrl.GitProvider, GitUser: clctrl.GitAuth.User, diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 026454b4..9624cf66 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -10,18 +10,17 @@ import ( "context" "fmt" "net/http" - "os" "time" "github.com/kubefirst/kubefirst-api/internal/constants" "github.com/kubefirst/kubefirst-api/internal/db" + "github.com/kubefirst/kubefirst-api/internal/env" "github.com/kubefirst/kubefirst-api/internal/utils" google "github.com/kubefirst/kubefirst-api/pkg/google" "github.com/kubefirst/kubefirst-api/pkg/handlers" "github.com/kubefirst/kubefirst-api/pkg/providerConfigs" pkgtypes "github.com/kubefirst/kubefirst-api/pkg/types" "github.com/kubefirst/metrics-client/pkg/telemetry" - "github.com/kubefirst/runtime/pkg" runtime "github.com/kubefirst/runtime/pkg" awsinternal "github.com/kubefirst/runtime/pkg/aws" "github.com/kubefirst/runtime/pkg/github" @@ -143,21 +142,23 @@ func (clctrl *ClusterController) InitController(def *pkgtypes.ClusterDefinition) clusterID = runtime.GenerateClusterID() } + env, _ := env.GetEnv() + telemetryEvent := telemetry.TelemetryEvent{ - CliVersion: os.Getenv("KUBEFIRST_VERSION"), - CloudProvider: os.Getenv("CLOUD_PROVIDER"), - ClusterID: os.Getenv("CLUSTER_ID"), - ClusterType: os.Getenv("CLUSTER_TYPE"), - DomainName: os.Getenv("DOMAIN_NAME"), + CliVersion: env.KubefirstVersion, + CloudProvider: env.CloudProvider, + ClusterID: env.ClusterId, + ClusterType: env.ClusterType, + DomainName: env.DomainName, ErrorMessage: "", - GitProvider: os.Getenv("GIT_PROVIDER"), - InstallMethod: os.Getenv("INSTALL_METHOD"), + GitProvider: env.GitProvider, + InstallMethod: env.InstallMethod, KubefirstClient: "api", - KubefirstTeam: os.Getenv("KUBEFIRST_TEAM"), - KubefirstTeamInfo: os.Getenv("KUBEFIRST_TEAM_INFO"), - MachineID: os.Getenv("CLUSTER_ID"), + KubefirstTeam: env.KubefirstTeam, + KubefirstTeamInfo: env.KubefirstTeamInfo, + MachineID: env.ClusterId, MetricName: telemetry.ClusterInstallStarted, - UserId: os.Getenv("CLUSTER_ID"), + UserId: env.ClusterId, } clctrl.TelemetryEvent = telemetryEvent @@ -206,11 +207,9 @@ func (clctrl *ClusterController) InitController(def *pkgtypes.ClusterDefinition) clctrl.KubefirstStateStoreBucketName = fmt.Sprintf("k1-state-store-%s-%s", clctrl.ClusterName, clusterID) clctrl.KubefirstArtifactsBucketName = fmt.Sprintf("k1-artifacts-%s-%s", clctrl.ClusterName, clusterID) - clctrl.KubefirstTeam = os.Getenv("KUBEFIRST_TEAM") - if clctrl.KubefirstTeam == "" { - clctrl.KubefirstTeam = "undefined" - } - clctrl.AtlantisWebhookSecret = pkg.Random(20) + clctrl.KubefirstTeam = env.KubefirstTeam + + clctrl.AtlantisWebhookSecret = runtime.Random(20) var fullDomainName string if clctrl.SubdomainName != "" { diff --git a/internal/db/mongo.go b/internal/db/mongo.go index cc48e605..c54822fe 100644 --- a/internal/db/mongo.go +++ b/internal/db/mongo.go @@ -11,6 +11,7 @@ import ( "fmt" "os" + "github.com/kubefirst/kubefirst-api/internal/env" pkgtypes "github.com/kubefirst/kubefirst-api/pkg/types" "github.com/kubefirst/runtime/pkg/k8s" log "github.com/sirupsen/logrus" @@ -34,25 +35,31 @@ var Client = Connect() // Connect func Connect() *MongoDBClient { + env, getEnvError := env.GetEnv() + + if getEnvError != nil { + log.Fatal(getEnvError.Error()) + } + var connString string var clientOptions *options.ClientOptions ctx := context.Background() - switch os.Getenv("MONGODB_HOST_TYPE") { + switch env.MongoDBHostType { case "atlas": serverAPI := options.ServerAPI(options.ServerAPIVersion1) connString = fmt.Sprintf("mongodb+srv://%s:%s@%s", - os.Getenv("MONGODB_USERNAME"), - os.Getenv("MONGODB_PASSWORD"), - os.Getenv("MONGODB_HOST"), + env.MongoDBUsername, + env.MongoDBPassword, + env.MongoDBHost, ) clientOptions = options.Client().ApplyURI(connString).SetServerAPIOptions(serverAPI) case "local": connString = fmt.Sprintf("mongodb://%s:%s@%s/?authSource=admin", - os.Getenv("MONGODB_USERNAME"), - os.Getenv("MONGODB_PASSWORD"), - os.Getenv("MONGODB_HOST"), + env.MongoDBUsername, + env.MongoDBPassword, + env.MongoDBHost, ) clientOptions = options.Client().ApplyURI(connString) } @@ -81,7 +88,9 @@ func (mdbcl *MongoDBClient) TestDatabaseConnection(silent bool) error { log.Fatalf("error connecting to mongodb: %s", err) } if !silent { - log.Infof("connected to mongodb host %s", os.Getenv("MONGODB_HOST")) + env, _ := env.GetEnv() + + log.Infof("connected to mongodb host %s", env.MongoDBHost) } return nil @@ -89,6 +98,8 @@ func (mdbcl *MongoDBClient) TestDatabaseConnection(silent bool) error { // ImportClusterIfEmpty func (mdbcl *MongoDBClient) ImportClusterIfEmpty(silent bool) (pkgtypes.Cluster, error) { + env, _ := env.GetEnv() + log.SetFormatter(&log.TextFormatter{ FullTimestamp: true, TimestampFormat: "", @@ -98,7 +109,7 @@ func (mdbcl *MongoDBClient) ImportClusterIfEmpty(silent bool) (pkgtypes.Cluster, // find the secret in mgmt cluster's kubefirst namespace and read import payload and clustername var kcfg *k8s.KubernetesClient - if os.Getenv("IS_CLUSTER_ZERO") == "true" { + if env.IsClusterZero { log.Info("IS_CLUSTER_ZERO is set to true, skipping import cluster logic.") return pkgtypes.Cluster{}, nil } @@ -109,12 +120,7 @@ func (mdbcl *MongoDBClient) ImportClusterIfEmpty(silent bool) (pkgtypes.Cluster, } clusterDir := fmt.Sprintf("%s/.k1/%s", homeDir, "") - inCluster := false - if os.Getenv("IN_CLUSTER") == "true" { - inCluster = true - } - - kcfg = k8s.CreateKubeConfig(inCluster, fmt.Sprintf("%s/kubeconfig", clusterDir)) + kcfg = k8s.CreateKubeConfig(env.InCluster, fmt.Sprintf("%s/kubeconfig", clusterDir)) log.Infof("reading secret mongo-state to determine if import is needed") secData, err := k8s.ReadSecretV2(kcfg.Clientset, "kubefirst", "mongodb-state") @@ -169,6 +175,8 @@ type EstablishConnectArgs struct { } func (mdbcl *MongoDBClient) EstablishMongoConnection(args EstablishConnectArgs) error { + env, _ := env.GetEnv() + var pingError error for tries := 0; tries < args.Tries; tries += 1 { @@ -181,7 +189,7 @@ func (mdbcl *MongoDBClient) EstablishMongoConnection(args EstablishConnectArgs) } if !args.Silent { - log.Infof("connected to mongodb host %s", os.Getenv("MONGODB_HOST")) + log.Infof("connected to mongodb host %s", env.MongoDBHost) } return nil diff --git a/internal/env/env.go b/internal/env/env.go new file mode 100644 index 00000000..c2946fb4 --- /dev/null +++ b/internal/env/env.go @@ -0,0 +1,46 @@ +package env + +import ( + env "github.com/caarlos0/env/v10" + "github.com/joho/godotenv" + log "github.com/sirupsen/logrus" +) + +type Env struct { + ServerPort int `env:"SERVER_PORT" envDefault:"8081"` + K1AccessToken string `env:"K1_ACCESS_TOKEN"` + MongoDBHost string `env:"MONGODB_HOST,notEmpty"` + MongoDBHostType string `env:"MONGODB_HOST_TYPE,notEmpty"` + MongoDBUsername string `env:"MONGODB_USERNAME,notEmpty"` + MongoDBPassword string `env:"MONGODB_PASSWORD,notEmpty"` + KubefirstVersion string `env:"KUBEFIRST_VERSION" envDefault:"development"` + CloudProvider string `env:"CLOUD_PROVIDER"` + ClusterId string `env:"CLUSTER_ID"` + ClusterType string `env:"CLUSTER_TYPE"` + DomainName string `env:"DOMAIN_NAME"` + GitProvider string `env:"GIT_PROVIDER"` + InstallMethod string `env:"INSTALL_METHOD"` + KubefirstTeam string `env:"KUBEFIRST_TEAM" envDefault:"undefined"` + KubefirstTeamInfo string `env:"KUBEFIRST_TEAM_INFO"` + AWSRegion string `env:"AWS_REGION"` + AWSProfile string `env:"AWS_PROFILE"` + IsClusterZero bool `env:"IS_CLUSTER_ZERO"` + InCluster bool `env:"IN_CLUSTER" envDefault:"false"` + EnterpriseApiUrl string `env:"ENTERPRISE_API_URL"` +} + +func GetEnv()(Env,error) { + envError := godotenv.Load(".env") + + if envError != nil { + log.Info("error loading .env file, using local environment variables") + } + + environment := Env{} + err := env.Parse(&environment) + if err != nil { + return Env{}, err + } + + return environment, nil +} \ No newline at end of file diff --git a/internal/env/env_test.go b/internal/env/env_test.go new file mode 100644 index 00000000..2d014fa8 --- /dev/null +++ b/internal/env/env_test.go @@ -0,0 +1,138 @@ +package env + +import ( + "os" + "testing" +) + +func TestEnv(t *testing.T) { + os.Setenv("SERVER_PORT", "8081") + os.Setenv("K1_ACCESS_TOKEN", "k1_access_token") + os.Setenv("MONGODB_HOST", "mongodb_host") + os.Setenv("MONGODB_HOST_TYPE", "mongodb_host_type") + os.Setenv("MONGODB_USERNAME", "mongodb_username") + os.Setenv("MONGODB_PASSWORD", "mongodb_password") + os.Setenv("KUBEFIRST_VERSION", "development") + os.Setenv("CLOUD_PROVIDER", "cloud_provider") + os.Setenv("CLUSTER_ID", "cluster_id") + os.Setenv("CLUSTER_TYPE", "cluster_type") + os.Setenv("DOMAIN_NAME", "domain_name") + os.Setenv("GIT_PROVIDER", "git_provider") + os.Setenv("INSTALL_METHOD", "install_method") + os.Setenv("KUBEFIRST_TEAM", "kubefirst_team") + os.Setenv("KUBEFIRST_TEAM_INFO", "kubefirst_team_info") + os.Setenv("AWS_REGION", "aws_region") + os.Setenv("AWS_PROFILE", "aws_profile") + os.Setenv("IS_CLUSTER_ZERO", "true") + os.Setenv("IN_CLUSTER", "false") + os.Setenv("ENTERPRISE_API_URL", "enterprise_api_url") + + defer func() { + os.Unsetenv("SERVER_PORT") + os.Unsetenv("K1_ACCESS_TOKEN") + os.Unsetenv("MONGODB_HOST") + os.Unsetenv("MONGODB_HOST_TYPE") + os.Unsetenv("MONGODB_USERNAME") + os.Unsetenv("MONGODB_PASSWORD") + os.Unsetenv("KUBEFIRST_VERSION") + os.Unsetenv("CLOUD_PROVIDER") + os.Unsetenv("CLUSTER_ID") + os.Unsetenv("CLUSTER_TYPE") + os.Unsetenv("DOMAIN_NAME") + os.Unsetenv("GIT_PROVIDER") + os.Unsetenv("INSTALL_METHOD") + os.Unsetenv("KUBEFIRST_TEAM") + os.Unsetenv("KUBEFIRST_TEAM_INFO") + os.Unsetenv("AWS_REGION") + os.Unsetenv("AWS_PROFILE") + os.Unsetenv("IS_CLUSTER_ZERO") + os.Unsetenv("IN_CLUSTER") + os.Unsetenv("ENTERPRISE_API_URL") + }() + + env := Env{} + env, err := GetEnv() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if env.ServerPort != 8081 { + t.Errorf("expected ServerPort to be 8081, but got %d", env.ServerPort) + } + + if env.K1AccessToken != "k1_access_token" { + t.Errorf("expected K1AccessToken to be 'k1_access_token', but got '%s'", env.K1AccessToken) + } + + if env.MongoDBHost != "mongodb_host" { + t.Errorf("expected MongoDBHost to be 'mongodb_host', but got '%s'", env.MongoDBHost) + } + + if env.MongoDBHostType != "mongodb_host_type" { + t.Errorf("expected MongoDBHostType to be 'mongodb_host_type', but got '%s'", env.MongoDBHostType) + } + + if env.MongoDBUsername != "mongodb_username" { + t.Errorf("expected MongoDBUsername to be 'mongodb_username', but got '%s'", env.MongoDBUsername) + } + + if env.MongoDBPassword != "mongodb_password" { + t.Errorf("expected MongoDBPassword to be 'mongodb_password', but got '%s'", env.MongoDBPassword) + } + + if env.KubefirstVersion != "development" { + t.Errorf("expected KubefirstVersion to be 'development', but got '%s'", env.KubefirstVersion) + } + + if env.CloudProvider != "cloud_provider" { + t.Errorf("expected CloudProvider to be 'cloud_provider', but got '%s'", env.CloudProvider) + } + + if env.ClusterId != "cluster_id" { + t.Errorf("expected ClusterId to be 'cluster_id', but got '%s'", env.ClusterId) + } + + if env.ClusterType != "cluster_type" { + t.Errorf("expected ClusterType to be 'cluster_type', but got '%s'", env.ClusterType) + } + + if env.DomainName != "domain_name" { + t.Errorf("expected DomainName to be 'domain_name', but got '%s'", env.DomainName) + } + + if env.GitProvider != "git_provider" { + t.Errorf("expected GitProvider to be 'git_provider', but got '%s'", env.GitProvider) + } + + if env.InstallMethod != "install_method" { + t.Errorf("expected InstallMethod to be 'install_method', but got '%s'", env.InstallMethod) + } + + if env.KubefirstTeam != "kubefirst_team" { + t.Errorf("expected KubefirstTeam to be 'kubefirst_team', but got '%s'", env.KubefirstTeam) + } + + if env.KubefirstTeamInfo != "kubefirst_team_info" { + t.Errorf("expected KubefirstTeamInfo to be 'kubefirst_team_info', but got '%s'", env.KubefirstTeamInfo) + } + + if env.AWSRegion != "aws_region" { + t.Errorf("expected AWSRegion to be 'aws_region', but got '%s'", env.AWSRegion) + } + + if env.AWSProfile != "aws_profile" { + t.Errorf("expected AWSProfile to be 'aws_profile', but got '%s'", env.AWSProfile) + } + + if env.IsClusterZero != true { + t.Errorf("expected IsClusterZero to be true, but got false") + } + + if env.InCluster != false { + t.Errorf("expected InCluster to be false, but got true") + } + + if env.EnterpriseApiUrl != "enterprise_api_url" { + t.Errorf("expected EnterpriseApiUrl to be 'enterprise_api_url', but got '%s'", env.EnterpriseApiUrl) + } +} \ No newline at end of file diff --git a/internal/environments/defaultEnvironments.go b/internal/environments/defaultEnvironments.go index 4d656d0d..6b24542d 100644 --- a/internal/environments/defaultEnvironments.go +++ b/internal/environments/defaultEnvironments.go @@ -17,6 +17,7 @@ import ( "time" "github.com/kubefirst/kubefirst-api/internal/db" + "github.com/kubefirst/kubefirst-api/internal/env" "github.com/kubefirst/kubefirst-api/pkg/types" log "github.com/sirupsen/logrus" "go.mongodb.org/mongo-driver/bson/primitive" @@ -97,12 +98,6 @@ func CreateDefaultEnvironments(mgmtCluster types.Cluster) error { } func callApiEE(goPayload types.WorkloadClusterSet) error { - - - // in cluster url - KubefirstApiEe := os.Getenv("ENTERPRISE_API_URL") - - customTransport := http.DefaultTransport.(*http.Transport).Clone() customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} httpClient := http.Client{Transport: customTransport} @@ -112,7 +107,9 @@ func callApiEE(goPayload types.WorkloadClusterSet) error { return err } - req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/environments/%s", KubefirstApiEe, os.Getenv("CLUSTER_ID")), bytes.NewReader(payload)) + env, _ := env.GetEnv() + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/environments/%s", env.EnterpriseApiUrl, env.ClusterId), bytes.NewReader(payload)) if err != nil { log.Errorf("error creating http request %s", err) @@ -143,7 +140,7 @@ func callApiEE(goPayload types.WorkloadClusterSet) error { return err } - log.Infof("Default environments initiatied", string(body)) + log.Info("Default environments initiatied", string(body)) return nil } diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 8c3ec78b..0e23b559 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -8,27 +8,30 @@ package middleware import ( "net/http" - "os" "strings" "github.com/gin-gonic/gin" + "github.com/kubefirst/kubefirst-api/internal/env" "github.com/rs/zerolog/log" ) // ValidateAPIKey determines whether or not a request is authenticated with a valid API key func ValidateAPIKey() gin.HandlerFunc { + return func(c *gin.Context) { APIKey := strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer ") - + if APIKey == "" { c.JSON(http.StatusUnauthorized, gin.H{"status": 401, "message": "Authentication failed - no API key provided in request"}) c.Abort() - + log.Info().Msgf(" Request Status: 401; Authentication failed - no API key provided in request") return } - apiToken := os.Getenv("K1_ACCESS_TOKEN") - if APIKey != apiToken { + + env, _ := env.GetEnv() + + if APIKey != env.K1AccessToken { c.JSON(http.StatusUnauthorized, gin.H{"status": 401, "message": "Authentication failed - not a valid API key"}) c.Abort() diff --git a/internal/router/api/v1/cluster.go b/internal/router/api/v1/cluster.go index 4c8c0232..a9e642f6 100644 --- a/internal/router/api/v1/cluster.go +++ b/internal/router/api/v1/cluster.go @@ -8,13 +8,13 @@ package api import ( "fmt" - "os" "net/http" "github.com/gin-gonic/gin" "github.com/kubefirst/kubefirst-api/internal/constants" "github.com/kubefirst/kubefirst-api/internal/db" + "github.com/kubefirst/kubefirst-api/internal/env" "github.com/kubefirst/kubefirst-api/internal/gitShim" "github.com/kubefirst/kubefirst-api/internal/services" "github.com/kubefirst/kubefirst-api/internal/types" @@ -60,8 +60,10 @@ func DeleteCluster(c *gin.Context) { return } + env, _ := env.GetEnv() + telemetryEvent := telemetry.TelemetryEvent{ - CliVersion: os.Getenv("KUBEFIRST_VERSION"), + CliVersion: env.KubefirstVersion, CloudProvider: rec.CloudProvider, ClusterID: rec.ClusterID, ClusterType: rec.ClusterType, @@ -69,8 +71,8 @@ func DeleteCluster(c *gin.Context) { GitProvider: rec.GitProvider, InstallMethod: "", KubefirstClient: "api", - KubefirstTeam: os.Getenv("KUBEFIRST_TEAM"), - KubefirstTeamInfo: os.Getenv("KUBEFIRST_TEAM_INFO"), + KubefirstTeam: env.KubefirstTeam, + KubefirstTeamInfo: env.KubefirstTeamInfo, MachineID: rec.DomainName, ErrorMessage: "", UserId: rec.DomainName, @@ -220,8 +222,6 @@ func GetClusters(c *gin.Context) { // @Param Authorization header string true "API key" default(Bearer ) // PostCreateCluster handles a request to create a cluster func PostCreateCluster(c *gin.Context) { - // jsonData, err := io.ReadAll(c.Request.Body) - // fmt.Spintf(string(jsonData)) clusterName, param := c.Params.Get("cluster_name") if !param || string(clusterName) == ":cluster_name" { c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ @@ -280,15 +280,13 @@ func PostCreateCluster(c *gin.Context) { } // Determine authentication type - inCluster := false useSecretForAuth := false var k1AuthSecret = map[string]string{} - if os.Getenv("IN_CLUSTER") == "true" { - inCluster = true - } - if inCluster { - kcfg := k8s.CreateKubeConfig(inCluster, "") + env, _ := env.GetEnv() + + if env.InCluster { + kcfg := k8s.CreateKubeConfig(env.InCluster, "") k1AuthSecret, err := k8s.ReadSecretV2(kcfg.Clientset, constants.KubefirstNamespace, constants.KubefirstAuthSecretName) if err != nil { log.Warnf("authentication secret does not exist, continuing: %s", err) diff --git a/internal/router/api/v1/telemetry.go b/internal/router/api/v1/telemetry.go index f446513a..b9d90678 100644 --- a/internal/router/api/v1/telemetry.go +++ b/internal/router/api/v1/telemetry.go @@ -8,10 +8,10 @@ package api import ( "net/http" - "os" "github.com/gin-gonic/gin" "github.com/kubefirst/kubefirst-api/internal/db" + "github.com/kubefirst/kubefirst-api/internal/env" "github.com/kubefirst/kubefirst-api/internal/types" "github.com/kubefirst/metrics-client/pkg/telemetry" ) @@ -44,8 +44,11 @@ func PostTelemetry(c *gin.Context) { }) return } + + env, _ := env.GetEnv() + telEvent := telemetry.TelemetryEvent{ - CliVersion: os.Getenv("KUBEFIRST_VERSION"), + CliVersion: env.KubefirstVersion, CloudProvider: cl.CloudProvider, ClusterID: cl.ClusterID, ClusterType: cl.ClusterType, @@ -53,8 +56,8 @@ func PostTelemetry(c *gin.Context) { GitProvider: cl.GitProvider, InstallMethod: "", KubefirstClient: "api", - KubefirstTeam: os.Getenv("KUBEFIRST_TEAM"), - KubefirstTeamInfo: os.Getenv("KUBEFIRST_TEAM_INFO"), + KubefirstTeam: env.KubefirstTeam, + KubefirstTeamInfo: env.KubefirstTeamInfo, MachineID: cl.DomainName, ErrorMessage: "", UserId: cl.DomainName, diff --git a/internal/services/services.go b/internal/services/services.go index 02b3db1d..9302622a 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -21,6 +21,7 @@ import ( vaultapi "github.com/hashicorp/vault/api" "github.com/kubefirst/kubefirst-api/internal/constants" "github.com/kubefirst/kubefirst-api/internal/db" + "github.com/kubefirst/kubefirst-api/internal/env" "github.com/kubefirst/kubefirst-api/internal/gitShim" "github.com/kubefirst/kubefirst-api/internal/gitopsCatalog" "github.com/kubefirst/kubefirst-api/internal/types" @@ -94,12 +95,9 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *types.Gitop var kcfg *k8s.KubernetesClient - inCluster := false - if os.Getenv("IN_CLUSTER") == "true" { - inCluster = true - } + env, _ := env.GetEnv() - kcfg = k8s.CreateKubeConfig(inCluster, fmt.Sprintf("%s/kubeconfig", tmpGitopsDir)) + kcfg = k8s.CreateKubeConfig(env.InCluster, fmt.Sprintf("%s/kubeconfig", tmpGitopsDir)) var fullDomainName string if cl.SubdomainName != "" { diff --git a/main.go b/main.go index b4567871..b5804ebd 100644 --- a/main.go +++ b/main.go @@ -8,11 +8,10 @@ package main import ( "fmt" - "os" - "github.com/joho/godotenv" "github.com/kubefirst/kubefirst-api/docs" "github.com/kubefirst/kubefirst-api/internal/db" + "github.com/kubefirst/kubefirst-api/internal/env" "github.com/kubefirst/kubefirst-api/internal/environments" api "github.com/kubefirst/kubefirst-api/internal/router" "github.com/kubefirst/kubefirst-api/internal/services" @@ -31,16 +30,12 @@ import ( // @host localhost:port // @BasePath /api/v1 -const ( - port int = 8081 -) - func main() { - envError := godotenv.Load(".env") + env, err := env.GetEnv() - if envError != nil { - log.Info("error loading .env file, using local environment variables") + if err != nil { + log.Fatal(err.Error()) } log.SetFormatter(&log.TextFormatter{ @@ -49,18 +44,8 @@ func main() { }) log.SetReportCaller(false) - // Check for required environment variables - if os.Getenv("MONGODB_HOST_TYPE") == "" { - log.Fatalf("the MONGODB_HOST_TYPE environment variable must be set to either: atlas, local") - } - for _, v := range []string{"MONGODB_HOST", "MONGODB_USERNAME", "MONGODB_PASSWORD"} { - if os.Getenv(v) == "" { - log.Fatalf("the %s environment variable must be set", v) - } - } - // Verify database connectivity - err := db.Client.EstablishMongoConnection(db.EstablishConnectArgs{ + err = db.Client.EstablishMongoConnection(db.EstablishConnectArgs{ Tries: 20, Silent: false, }) @@ -93,26 +78,26 @@ func main() { docs.SwaggerInfo.Title = "Kubefirst API" docs.SwaggerInfo.Description = "Kubefirst API" docs.SwaggerInfo.Version = "1.0" - docs.SwaggerInfo.Host = fmt.Sprintf("localhost:%v", port) + docs.SwaggerInfo.Host = fmt.Sprintf("localhost:%v", env.ServerPort) docs.SwaggerInfo.BasePath = "/api/v1" docs.SwaggerInfo.Schemes = []string{"http"} // Telemetry handler telemetryEvent := telemetry.TelemetryEvent{ - CliVersion: os.Getenv("KUBEFIRST_VERSION"), - CloudProvider: os.Getenv("CLOUD_PROVIDER"), - ClusterID: os.Getenv("CLUSTER_ID"), - ClusterType: os.Getenv("CLUSTER_TYPE"), - DomainName: os.Getenv("DOMAIN_NAME"), + CliVersion: env.KubefirstVersion, + CloudProvider: env.CloudProvider, + ClusterID: env.ClusterId, + ClusterType: env.ClusterType, + DomainName: env.DomainName, ErrorMessage: "", - GitProvider: os.Getenv("GIT_PROVIDER"), - InstallMethod: os.Getenv("INSTALL_METHOD"), + GitProvider: env.GitProvider, + InstallMethod: env.InstallMethod, KubefirstClient: "api", - KubefirstTeam: os.Getenv("KUBEFIRST_TEAM"), - KubefirstTeamInfo: os.Getenv("KUBEFIRST_TEAM_INFO"), - MachineID: os.Getenv("CLUSTER_ID"), + KubefirstTeam: env.KubefirstTeam, + KubefirstTeamInfo: env.KubefirstTeamInfo, + MachineID: env.ClusterId, MetricName: telemetry.ClusterInstallStarted, - UserId: os.Getenv("CLUSTER_ID"), + UserId: env.ClusterId, } // Subroutine to automatically update gitops catalog @@ -123,7 +108,7 @@ func main() { // API r := api.SetupRouter() - err = r.Run(fmt.Sprintf(":%v", port)) + err = r.Run(fmt.Sprintf(":%v", env.ServerPort)) if err != nil { log.Fatalf("Error starting API: %s", err) }