| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- package api
- import (
- "fmt"
- "net/http"
- "strconv"
- "strings"
- "github.com/go-playground/locales/en"
- ut "github.com/go-playground/universal-translator"
- vr "github.com/go-playground/validator/v10"
- "github.com/porter-dev/porter/internal/auth/sessionstore"
- "github.com/porter-dev/porter/internal/auth/token"
- "github.com/porter-dev/porter/internal/kubernetes/local"
- "github.com/porter-dev/porter/internal/notifier/sendgrid"
- "github.com/porter-dev/porter/internal/oauth"
- "golang.org/x/oauth2"
- "gorm.io/gorm"
- "github.com/gorilla/sessions"
- "github.com/porter-dev/porter/internal/helm"
- "github.com/porter-dev/porter/internal/helm/loader"
- "github.com/porter-dev/porter/internal/kubernetes"
- lr "github.com/porter-dev/porter/internal/logger"
- notif "github.com/porter-dev/porter/internal/notifier"
- "github.com/porter-dev/porter/internal/repository"
- "github.com/porter-dev/porter/internal/validator"
- "helm.sh/helm/v3/pkg/storage"
- "github.com/porter-dev/porter/internal/analytics"
- "github.com/porter-dev/porter/internal/config"
- )
- // TestAgents are the k8s agents used for testing
- type TestAgents struct {
- HelmAgent *helm.Agent
- HelmTestStorageDriver *storage.Storage
- K8sAgent *kubernetes.Agent
- }
- // AppConfig is the configuration required for creating a new App
- type AppConfig struct {
- DB *gorm.DB
- Logger *lr.Logger
- Repository repository.Repository
- ServerConf config.ServerConf
- RedisConf *config.RedisConf
- DBConf config.DBConf
- CapConf config.CapConf
- // TestAgents if API is in testing mode
- TestAgents *TestAgents
- }
- // App represents an API instance with handler methods attached, a DB connection
- // and a logger instance
- type App struct {
- // Server configuration
- ServerConf config.ServerConf
- // Logger for logging
- Logger *lr.Logger
- // Repo implements a query repository
- Repo repository.Repository
- // session store for cookie-based sessions
- Store sessions.Store
- // agents exposed for testing
- TestAgents *TestAgents
- // An in-cluster agent if service is running in cluster
- ProvisionerAgent *kubernetes.Agent
- IngressAgent *kubernetes.Agent
- // redis client for redis connection
- RedisConf *config.RedisConf
- // config for db
- DBConf config.DBConf
- // config for capabilities
- Capabilities *AppCapabilities
- // ChartLookupURLs contains an in-memory store of Porter chart names matched with
- // a repo URL, so that finding a chart does not involve multiple lookups to our
- // chart repo's index.yaml file
- ChartLookupURLs map[string]string
- // oauth-specific clients
- GithubUserConf *oauth2.Config
- GithubProjectConf *oauth2.Config
- GithubAppConf *oauth.GithubAppConf
- DOConf *oauth2.Config
- GoogleUserConf *oauth2.Config
- SlackConf *oauth2.Config
- db *gorm.DB
- validator *vr.Validate
- translator *ut.Translator
- tokenConf *token.TokenGeneratorConf
- analyticsClient analytics.AnalyticsSegmentClient
- notifier notif.UserNotifier
- }
- type AppCapabilities struct {
- Provisioning bool `json:"provisioner"`
- Github bool `json:"github"`
- BasicLogin bool `json:"basic_login"`
- GithubLogin bool `json:"github_login"`
- GoogleLogin bool `json:"google_login"`
- SlackNotifications bool `json:"slack_notifs"`
- Email bool `json:"email"`
- Analytics bool `json:"analytics"`
- }
- // New returns a new App instance
- func New(conf *AppConfig) (*App, error) {
- // create a new validator and translator
- validator := validator.New()
- en := en.New()
- uni := ut.New(en, en)
- translator, found := uni.GetTranslator("en")
- if !found {
- return nil, fmt.Errorf("could not find \"en\" translator")
- }
- app := &App{
- Logger: conf.Logger,
- Repo: conf.Repository,
- ServerConf: conf.ServerConf,
- RedisConf: conf.RedisConf,
- DBConf: conf.DBConf,
- TestAgents: conf.TestAgents,
- Capabilities: &AppCapabilities{},
- db: conf.DB,
- validator: validator,
- translator: &translator,
- }
- // if repository not specified, default to in-memory
- // if app.Repo == nil {
- // app.Repo = test.NewRepository(true)
- // }
- // create the session store
- store, err := sessionstore.NewStore(app.Repo, app.ServerConf)
- if err != nil {
- return nil, err
- }
- app.Store = store
- sc := conf.ServerConf
- // get the InClusterAgent from either a file-based kubeconfig or the in-cluster agent
- app.assignProvisionerAgent(&sc)
- app.assignIngressAgent(&sc)
- // if server config contains OAuth client info, create clients
- if sc.GithubClientID != "" && sc.GithubClientSecret != "" {
- app.Capabilities.Github = true
- app.GithubUserConf = oauth.NewGithubClient(&oauth.Config{
- ClientID: sc.GithubClientID,
- ClientSecret: sc.GithubClientSecret,
- Scopes: []string{"read:user", "user:email"},
- BaseURL: sc.ServerURL,
- })
- app.GithubProjectConf = oauth.NewGithubClient(&oauth.Config{
- ClientID: sc.GithubClientID,
- ClientSecret: sc.GithubClientSecret,
- Scopes: []string{"repo", "read:user", "workflow"},
- BaseURL: sc.ServerURL,
- })
- app.Capabilities.GithubLogin = sc.GithubLoginEnabled
- }
- if sc.GithubAppClientID != "" &&
- sc.GithubAppClientSecret != "" &&
- sc.GithubAppName != "" &&
- sc.GithubAppWebhookSecret != "" &&
- sc.GithubAppSecretPath != "" &&
- sc.GithubAppID != "" {
- if AppID, err := strconv.ParseInt(sc.GithubAppID, 10, 64); err == nil {
- app.GithubAppConf = oauth.NewGithubAppClient(&oauth.Config{
- ClientID: sc.GithubAppClientID,
- ClientSecret: sc.GithubAppClientSecret,
- Scopes: []string{"read:user"},
- BaseURL: sc.ServerURL,
- }, sc.GithubAppName, sc.GithubAppWebhookSecret, sc.GithubAppSecretPath, AppID)
- }
- }
- if sc.GoogleClientID != "" && sc.GoogleClientSecret != "" {
- app.Capabilities.GoogleLogin = true
- app.GoogleUserConf = oauth.NewGoogleClient(&oauth.Config{
- ClientID: sc.GoogleClientID,
- ClientSecret: sc.GoogleClientSecret,
- Scopes: []string{
- "openid",
- "profile",
- "email",
- },
- BaseURL: sc.ServerURL,
- })
- }
- if sc.SlackClientID != "" && sc.SlackClientSecret != "" {
- app.Capabilities.SlackNotifications = true
- app.SlackConf = oauth.NewSlackClient(&oauth.Config{
- ClientID: sc.SlackClientID,
- ClientSecret: sc.SlackClientSecret,
- Scopes: []string{
- "incoming-webhook",
- "team:read",
- },
- BaseURL: sc.ServerURL,
- })
- }
- if sc.DOClientID != "" && sc.DOClientSecret != "" {
- app.DOConf = oauth.NewDigitalOceanClient(&oauth.Config{
- ClientID: sc.DOClientID,
- ClientSecret: sc.DOClientSecret,
- Scopes: []string{"read", "write"},
- BaseURL: sc.ServerURL,
- })
- }
- if sc.SendgridAPIKey != "" {
- app.Capabilities.Email = true
- sgClient := &sendgrid.Client{
- APIKey: sc.SendgridAPIKey,
- PWResetTemplateID: sc.SendgridPWResetTemplateID,
- PWGHTemplateID: sc.SendgridPWGHTemplateID,
- VerifyEmailTemplateID: sc.SendgridVerifyEmailTemplateID,
- ProjectInviteTemplateID: sc.SendgridProjectInviteTemplateID,
- SenderEmail: sc.SendgridSenderEmail,
- }
- app.notifier = sendgrid.NewUserNotifier(sgClient)
- }
- app.Capabilities.Analytics = sc.SegmentClientKey != ""
- app.Capabilities.BasicLogin = sc.BasicLoginEnabled
- app.tokenConf = &token.TokenGeneratorConf{
- TokenSecret: conf.ServerConf.TokenGeneratorSecret,
- }
- newSegmentClient := analytics.InitializeAnalyticsSegmentClient(sc.SegmentClientKey, app.Logger)
- app.analyticsClient = newSegmentClient
- app.updateChartRepoURLs()
- return app, nil
- }
- func (app *App) assignProvisionerAgent(sc *config.ServerConf) error {
- if sc.ProvisionerCluster == "kubeconfig" && sc.SelfKubeconfig != "" {
- app.Capabilities.Provisioning = true
- agent, err := local.GetSelfAgentFromFileConfig(sc.SelfKubeconfig)
- if err != nil {
- return fmt.Errorf("could not get in-cluster agent: %v", err)
- }
- app.ProvisionerAgent = agent
- return nil
- } else if sc.ProvisionerCluster == "kubeconfig" {
- return fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
- }
- app.Capabilities.Provisioning = true
- agent, err := kubernetes.GetAgentInClusterConfig()
- if err != nil {
- return fmt.Errorf("could not get in-cluster agent: %v", err)
- }
- app.ProvisionerAgent = agent
- return nil
- }
- func (app *App) assignIngressAgent(sc *config.ServerConf) error {
- if sc.IngressCluster == "kubeconfig" && sc.SelfKubeconfig != "" {
- agent, err := local.GetSelfAgentFromFileConfig(sc.SelfKubeconfig)
- if err != nil {
- return fmt.Errorf("could not get in-cluster agent: %v", err)
- }
- app.IngressAgent = agent
- return nil
- } else if sc.IngressCluster == "kubeconfig" {
- return fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
- }
- agent, err := kubernetes.GetAgentInClusterConfig()
- if err != nil {
- return fmt.Errorf("could not get in-cluster agent: %v", err)
- }
- app.IngressAgent = agent
- return nil
- }
- func (app *App) getTokenFromRequest(r *http.Request) *token.Token {
- reqToken := r.Header.Get("Authorization")
- splitToken := strings.Split(reqToken, "Bearer")
- if len(splitToken) != 2 {
- return nil
- }
- reqToken = strings.TrimSpace(splitToken[1])
- tok, err := token.GetTokenFromEncoded(reqToken, app.tokenConf)
- if err != nil {
- return nil
- }
- return tok
- }
- func (app *App) updateChartRepoURLs() {
- newCharts := make(map[string]string)
- for _, chartRepo := range []string{
- app.ServerConf.DefaultApplicationHelmRepoURL,
- app.ServerConf.DefaultAddonHelmRepoURL,
- } {
- indexFile, err := loader.LoadRepoIndexPublic(chartRepo)
- if err != nil {
- continue
- }
- for chartName, _ := range indexFile.Entries {
- newCharts[chartName] = chartRepo
- }
- }
- app.ChartLookupURLs = newCharts
- }
|