Skip to content
This repository has been archived by the owner on Mar 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2346 from ibuildthecloud/main
Browse files Browse the repository at this point in the history
Add login required for nested apps
  • Loading branch information
ibuildthecloud authored Nov 18, 2023
2 parents ba55def + 24f2918 commit d6f17e2
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 57 deletions.
7 changes: 4 additions & 3 deletions pkg/apis/internal.acorn.io/v1/appstatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ type AppStatus struct {
Routers map[string]RouterStatus `json:"routers,omitempty"`
Services map[string]ServiceStatus `json:"services,omitempty"`

Endpoints []Endpoint `json:"endpoints,omitempty"`
Stopped bool `json:"stopped,omitempty"`
Completed bool `json:"completed,omitempty"`
Endpoints []Endpoint `json:"endpoints,omitempty"`
Stopped bool `json:"stopped,omitempty"`
Completed bool `json:"completed,omitempty"`
LoginRequired bool `json:"loginRequired,omitempty"`
}

type DependencyNotFound struct {
Expand Down
4 changes: 4 additions & 0 deletions pkg/cli/builder/table/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
apiv1 "github.com/acorn-io/runtime/pkg/apis/api.acorn.io/v1"
adminv1 "github.com/acorn-io/runtime/pkg/apis/internal.admin.acorn.io/v1"
"github.com/acorn-io/runtime/pkg/labels"
"github.com/acorn-io/runtime/pkg/publicname"
"github.com/acorn-io/runtime/pkg/tags"
"github.com/rancher/wrangler/pkg/data/convert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -314,6 +315,9 @@ func MemoryToRange(obj any) (string, error) {
}

func AppGeneration(app apiv1.App, msg string) string {
if app.Status.AppStatus.LoginRequired {
return fmt.Sprintf("\"acorn login %s\" required", publicname.Get(&app))
}
if app.Generation != app.Status.ObservedGeneration {
return msg + "[update requested]"
}
Expand Down
19 changes: 17 additions & 2 deletions pkg/controller/appdefinition/acorn.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,23 @@ func toAcorns(req router.Request, appInstance *v1.AppInstance, tag name.Referenc
return result, nil
}

func scopeSecrets(app *v1.AppInstance, bindings v1.SecretBindings) (result v1.SecretBindings) {
func scopeSecrets(app *v1.AppInstance, bindings v1.SecretBindings, acornName string) (result v1.SecretBindings) {
seen := map[string]struct{}{}
for _, binding := range app.Spec.Secrets {
prefix := acornName + "."
if strings.HasPrefix(binding.Target, prefix) {
targetName := strings.TrimPrefix(binding.Target, prefix)
seen[targetName] = struct{}{}
result = append(result, v1.SecretBinding{
Secret: binding.Secret,
Target: targetName,
})
}
}
for _, binding := range bindings {
if _, ok := seen[binding.Target]; ok {
continue
}
binding.Secret = publicname.Get(app) + "." + binding.Secret
result = append(result, binding)
}
Expand Down Expand Up @@ -195,7 +210,7 @@ func toAcorn(appInstance *v1.AppInstance, tag name.Reference, pullSecrets *PullS
Annotations: append(acorn.Annotations, appInstance.Spec.Annotations...),
Image: image,
Volumes: acorn.Volumes,
Secrets: scopeSecrets(appInstance, acorn.Secrets),
Secrets: scopeSecrets(appInstance, acorn.Secrets, acornName),
PublishMode: publishMode,
Links: scopeLinks(appInstance, acorn.Links),
Profiles: acorn.Profiles,
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/appstatus/acorns.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ func (a *appStatusRenderer) readAcorns() error {
return err
}

if acorn.Status.AppStatus.LoginRequired {
a.app.Status.AppStatus.LoginRequired = true
}
s.Defined = true
s.UpToDate = acorn.Annotations[labels.AcornAppGeneration] == strconv.Itoa(int(a.app.Generation)) && acorn.Annotations[labels.AcornConfigHashAnnotation] == hash
s.Ready = s.UpToDate && acorn.Status.Ready
Expand Down
8 changes: 5 additions & 3 deletions pkg/controller/appstatus/appstatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func resetHandlerControlledFields(app *v1.AppInstance) {
func PrepareStatus(req router.Request, _ router.Response) error {
app := req.Object.(*v1.AppInstance)

app.Status.AppStatus.LoginRequired = false

if app.Status.AppStatus.Containers == nil {
app.Status.AppStatus.Containers = map[string]v1.ContainerStatus{}
}
Expand Down Expand Up @@ -117,7 +119,7 @@ func GetStatus(req router.Request, _ router.Response) error {

func SetStatus(req router.Request, _ router.Response) error {
app := req.Object.(*v1.AppInstance)
setMessages(req.Ctx, req.Client, app)
setMessages(app)

status := app.Status.AppStatus

Expand Down Expand Up @@ -204,12 +206,12 @@ func setCondition[T commonStatusGetter](obj kclient.Object, conditionName string
}
}

func setMessages(ctx context.Context, c kclient.Client, app *v1.AppInstance) {
func setMessages(app *v1.AppInstance) {
setContainerMessages(app)
setJobMessages(app)
setVolumeMessages(app)
setServiceMessages(app)
setSecretMessages(ctx, c, app)
setSecretMessages(app)
setAcornMessages(app)
setRouterMessages(app)
}
Expand Down
36 changes: 17 additions & 19 deletions pkg/controller/appstatus/secrets.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package appstatus

import (
"context"
"fmt"
"strconv"
"strings"
Expand All @@ -15,7 +14,6 @@ import (
"github.com/acorn-io/runtime/pkg/secrets"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
)

func linkedSecret(app *v1.AppInstance, name string) string {
Expand Down Expand Up @@ -53,6 +51,22 @@ func (a *appStatusRenderer) readSecrets() error {
},
}

if s.Missing && strings.HasPrefix(secretDef.Type, v1.SecretTypeCredentialPrefix) {
s.LoginRequired = true
s.TransitioningMessages = []string{fmt.Sprintf("missing: \"acorn login %s\" required", publicname.Get(a.app))}
a.app.Status.AppStatus.LoginRequired = true

instructionsData := secretDef.Params.GetData()["instructions"]
if instructions, _ := instructionsData.(string); instructions != "" {
result, err := secrets.NewInterpolator(a.ctx, a.c, a.app).Replace(instructions)
if err == nil {
s.LoginInstructions = result
} else {
s.LoginInstructions = instructions
}
}
}

secret := &corev1.Secret{}
if err := ref.Lookup(a.ctx, a.c, secret, a.app.Status.Namespace, secretName); apierrors.IsNotFound(err) {
a.app.Status.AppStatus.Secrets[secretName] = s
Expand Down Expand Up @@ -94,31 +108,15 @@ func (a *appStatusRenderer) readSecrets() error {
return nil
}

func setSecretMessages(ctx context.Context, c kclient.Client, app *v1.AppInstance) {
func setSecretMessages(app *v1.AppInstance) {
for secretName, s := range app.Status.AppStatus.Secrets {
// Not ready if we have any error messages
if len(s.ErrorMessages) > 0 {
s.Ready = false
}

if strings.HasPrefix(app.Status.AppSpec.Secrets[secretName].Type, v1.SecretTypeCredentialPrefix) {
instructionsData := app.Status.AppSpec.Secrets[secretName].Params.GetData()["instructions"]
if instructions, _ := instructionsData.(string); instructions != "" {
result, err := secrets.NewInterpolator(ctx, c, app).Replace(instructions)
if err == nil {
s.LoginInstructions = result
} else {
s.LoginInstructions = instructions
}
}
}

if s.Ready {
s.State = "created"
} else if s.Missing && strings.HasPrefix(app.Status.AppSpec.Secrets[secretName].Type, v1.SecretTypeCredentialPrefix) {
s.State = "pending"
s.LoginRequired = true
s.TransitioningMessages = []string{fmt.Sprintf("missing: \"acorn login %s\" required", publicname.Get(app))}
} else if s.UpToDate {
if len(s.ErrorMessages) > 0 {
s.State = "failing"
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/appstatus/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ func (a *appStatusRenderer) readServices() error {
if apierrors.IsNotFound(err) || err == nil {
s.ServiceAcornName = publicname.Get(serviceAcorn)
s.ServiceAcornReady = serviceAcorn.Status.Ready && serviceAcorn.Annotations[labels.AcornConfigHashAnnotation] == hash
if serviceAcorn.Status.AppStatus.LoginRequired {
a.app.Status.AppStatus.LoginRequired = true
}
} else {
return err
}
Expand Down
16 changes: 10 additions & 6 deletions pkg/dev/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/acorn-io/runtime/pkg/imagesource"
"github.com/acorn-io/runtime/pkg/labels"
"github.com/acorn-io/runtime/pkg/log"
"github.com/acorn-io/runtime/pkg/publicname"
"github.com/acorn-io/runtime/pkg/rulerequest"
"github.com/acorn-io/runtime/pkg/server/registry/apigroups/acorn/devsessions"
"github.com/acorn-io/z"
Expand All @@ -40,7 +41,7 @@ type Logger interface {
}

type AppStatusLogger interface {
AppStatus(ready bool, msg string)
AppStatus(ready bool, msg string, app *apiv1.App)
}

type Options struct {
Expand Down Expand Up @@ -70,13 +71,13 @@ type BuildStatus struct {
Message string
}

func (o *Options) complete() *Options {
func (o *Options) complete(ctx context.Context, c client.Client) *Options {
var cp Options
if o != nil {
cp = *o
}
if cp.Logger == nil {
cp.Logger = log.DefaultLogger
cp.Logger = log.NewDefaultLogger(ctx, c)
}

return &cp
Expand Down Expand Up @@ -204,7 +205,7 @@ func buildStart(c chan<- BuildStatus) {
}

func buildLoop(ctx context.Context, c client.Client, hash clientHash, opts *Options) error {
opts = opts.complete()
opts = opts.complete(ctx, c)

var (
watcher = watcher{
Expand Down Expand Up @@ -453,7 +454,7 @@ func appDeleteStop(ctx context.Context, c client.Client, logger AppStatusLogger,
w := objwatcher.New[*apiv1.App](wc)
_, err = w.ByName(ctx, c.GetNamespace(), appName, func(app *apiv1.App) (bool, error) {
if !app.DeletionTimestamp.IsZero() {
logger.AppStatus(false, fmt.Sprintf("app %s deleted, exiting", app.Name))
logger.AppStatus(false, fmt.Sprintf("app %s deleted, exiting", app.Name), app)
cancel()
return true, nil
}
Expand Down Expand Up @@ -493,6 +494,9 @@ func appStatusMessage(app *apiv1.App) (string, bool) {
// This is really only needed on the first run, before the controller runs
msg = "pending"
}
if app.Status.AppStatus.LoginRequired {
msg = fmt.Sprintf("\"acorn login %s\" required", publicname.Get(app))
}
return fmt.Sprintf("STATUS: ENDPOINTS[%s] HEALTHY[%s] UPTODATE[%s] %s",
app.Status.Columns.Endpoints,
app.Status.Columns.Healthy,
Expand All @@ -502,7 +506,7 @@ func appStatusMessage(app *apiv1.App) (string, bool) {

func PrintAppStatus(app *apiv1.App, logger AppStatusLogger) {
msg, ready := appStatusMessage(app)
logger.AppStatus(ready, msg)
logger.AppStatus(ready, msg, app)
}

func AppStatusLoop(ctx context.Context, c client.Client, logger AppStatusLogger, appName string) error {
Expand Down
36 changes: 30 additions & 6 deletions pkg/log/default_log.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,59 @@
package log

import (
"context"
"sync"

apiv1 "github.com/acorn-io/runtime/pkg/apis/api.acorn.io/v1"
"github.com/acorn-io/runtime/pkg/client"
"github.com/acorn-io/runtime/pkg/login"
"github.com/pterm/pterm"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var DefaultLogger = NewDefaultLogger()

func NewDefaultLogger() *DefaultLoggerImpl {
func NewDefaultLogger(ctx context.Context, c client.Client) *DefaultLoggerImpl {
return &DefaultLoggerImpl{
client: c,
ctx: ctx,
containerColors: map[string]pterm.Color{},
}
}

type DefaultLoggerImpl struct {
lock sync.Mutex
ctx context.Context
client client.Client
containerColors map[string]pterm.Color
lastLogin int64
}

func (*DefaultLoggerImpl) Errorf(format string, args ...interface{}) {
func (d *DefaultLoggerImpl) Errorf(format string, args ...interface{}) {
d.lock.Lock()
defer d.lock.Unlock()
logrus.Errorf(format, args...)
}

func (*DefaultLoggerImpl) Infof(format string, args ...interface{}) {
func (d *DefaultLoggerImpl) Infof(format string, args ...interface{}) {
d.lock.Lock()
defer d.lock.Unlock()
logrus.Infof(format, args...)
}

func (*DefaultLoggerImpl) AppStatus(ready bool, msg string) {
func (d *DefaultLoggerImpl) AppStatus(ready bool, msg string, app *apiv1.App) {
if ready {
pterm.DefaultBox.Println(pterm.LightGreen(msg))
} else {
d.lock.Lock()
defer d.lock.Unlock()
if app.Status.AppStatus.LoginRequired && app.Status.ObservedGeneration > d.lastLogin {
err := login.Secrets(d.ctx, d.client, app)
if err != nil {
go d.Errorf(err.Error())
} else {
d.lastLogin = app.Generation
}
}
pterm.Println(pterm.LightYellow(msg))
}
}
Expand Down
47 changes: 31 additions & 16 deletions pkg/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/acorn-io/runtime/pkg/client"
"github.com/acorn-io/runtime/pkg/prompt"
"github.com/charmbracelet/glamour"
apierror "k8s.io/apimachinery/pkg/api/errors"
)

func Secrets(ctx context.Context, c client.Client, app *apiv1.App) error {
Expand All @@ -22,25 +23,35 @@ func Secrets(ctx context.Context, c client.Client, app *apiv1.App) error {
}
}

//for _, acorn := range app.Status.AppStatus.Acorns {
// if acorn.AcornName != "" {
// if err := loginApp(ctx, c, app, acorn.AcornName); err != nil {
// return err
// }
// }
//}
//
//for _, service := range app.Status.AppStatus.Services {
// if service.ServiceAcornName != "" {
// if err := loginApp(ctx, c, app, service.ServiceAcornName); err != nil {
// return err
// }
// }
//}
for _, acorn := range app.Status.AppStatus.Acorns {
if acorn.AcornName != "" {
if err := loginApp(ctx, c, acorn.AcornName); err != nil {
return err
}
}
}

for _, service := range app.Status.AppStatus.Services {
if service.ServiceAcornName != "" {
if err := loginApp(ctx, c, service.ServiceAcornName); err != nil {
return err
}
}
}

return nil
}

func loginApp(ctx context.Context, c client.Client, appName string) error {
app, err := c.AppGet(ctx, appName)
if apierror.IsNotFound(err) {
return nil
} else if err != nil {
return err
}
return Secrets(ctx, c, app)
}

func secretIsOk(app *apiv1.App, secretName string) (string, bool) {
return app.Status.AppStatus.Secrets[secretName].LinkOverride,
!app.Status.AppStatus.Secrets[secretName].LoginRequired &&
Expand Down Expand Up @@ -69,7 +80,11 @@ func printInstructions(app *apiv1.App, secretName string) error {
}

func bindSecret(ctx context.Context, c client.Client, app *apiv1.App, targetSecretName, overrideSecretName string) error {
_, err := c.AppUpdate(ctx, app.Name, &client.AppUpdateOptions{
parts := append(strings.Split(app.Name, "."), targetSecretName)
appName := parts[0]
targetSecretName = strings.Join(parts[1:], ".")

_, err := c.AppUpdate(ctx, appName, &client.AppUpdateOptions{
Secrets: []v1.SecretBinding{
{
Secret: overrideSecretName,
Expand Down
Loading

0 comments on commit d6f17e2

Please sign in to comment.