diff --git a/integration/services/services_test.go b/integration/services/services_test.go index 6ec8f95ab..24fdc2eec 100644 --- a/integration/services/services_test.go +++ b/integration/services/services_test.go @@ -9,8 +9,45 @@ import ( "github.com/acorn-io/runtime/pkg/client" "github.com/acorn-io/runtime/pkg/labels" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func TestServiceInfo(t *testing.T) { + helper.StartController(t) + + ctx := helper.GetCTX(t) + c, _ := helper.ClientAndProject(t) + + image, err := c.AcornImageBuild(ctx, "./testdata/info/Acornfile", &client.AcornImageBuildOptions{ + Cwd: "./testdata/info", + }) + if err != nil { + t.Fatal(err) + } + + app, err := c.AppRun(ctx, image.ID, nil) + if err != nil { + t.Fatal(err) + } + + k, err := c.GetClient() + if err != nil { + t.Fatal(err) + } + + helper.WaitForObject(t, k.Watch, &apiv1.AppList{}, app, func(obj *apiv1.App) bool { + return obj.Status.Ready + }) + + i, err := c.AppInfo(ctx, app.Name) + require.NoError(t, err) + assert.Equal(t, "top message", i) + + i, err = c.AppInfo(ctx, app.Name+".nested") + require.NoError(t, err) + assert.Equal(t, "nested message", i) +} + func TestServices(t *testing.T) { helper.StartController(t) diff --git a/integration/services/testdata/info/Acornfile b/integration/services/testdata/info/Acornfile new file mode 100644 index 000000000..6017e9af6 --- /dev/null +++ b/integration/services/testdata/info/Acornfile @@ -0,0 +1,6 @@ +info: "top message" +acorns: nested: { + build: { + acornfile: "./nested.acorn" + } +} diff --git a/integration/services/testdata/info/nested.acorn b/integration/services/testdata/info/nested.acorn new file mode 100644 index 000000000..90354022c --- /dev/null +++ b/integration/services/testdata/info/nested.acorn @@ -0,0 +1 @@ +info: "nested message" diff --git a/pkg/cli/testdata/MockClient.go b/pkg/cli/testdata/MockClient.go index 797935b17..f9ea1e867 100644 --- a/pkg/cli/testdata/MockClient.go +++ b/pkg/cli/testdata/MockClient.go @@ -159,6 +159,11 @@ func (m *MockClient) DevSessionRelease(ctx context.Context, name string) error { panic("implement me") } +func (m *MockClient) AppInfo(ctx context.Context, name string) (string, error) { + //TODO implement me + panic("implement me") +} + func (m *MockClient) AppPullImage(ctx context.Context, name string) error { return nil } diff --git a/pkg/client/app.go b/pkg/client/app.go index 9b554af6d..2e6eb46ae 100644 --- a/pkg/client/app.go +++ b/pkg/client/app.go @@ -594,6 +594,26 @@ func (c *DefaultClient) AppConfirmUpgrade(ctx context.Context, name string) erro Body(&apiv1.ConfirmUpgrade{}).Do(ctx).Error() } +func (c *DefaultClient) AppInfo(ctx context.Context, name string) (string, error) { + app := &apiv1.App{} + err := c.Client.Get(ctx, kclient.ObjectKey{ + Name: name, + Namespace: c.Namespace, + }, app) + if err != nil { + return "", err + } + + info := &apiv1.AppInfo{} + err = c.RESTClient.Get(). + Namespace(app.Namespace). + Resource("apps"). + Name(app.Name). + SubResource("info"). + Do(ctx).Into(info) + return info.Info, err +} + func (c *DefaultClient) AppPullImage(ctx context.Context, name string) error { app := &apiv1.App{} err := c.Client.Get(ctx, kclient.ObjectKey{ diff --git a/pkg/client/client.go b/pkg/client/client.go index 3e501fee0..395ea89b4 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -214,6 +214,7 @@ type Client interface { AppRun(ctx context.Context, image string, opts *AppRunOptions) (*apiv1.App, error) AppUpdate(ctx context.Context, name string, opts *AppUpdateOptions) (*apiv1.App, error) AppLog(ctx context.Context, name string, opts *LogOptions) (<-chan apiv1.LogMessage, error) + AppInfo(ctx context.Context, name string) (string, error) AppConfirmUpgrade(ctx context.Context, name string) error AppPullImage(ctx context.Context, name string) error AppIgnoreDeleteCleanup(ctx context.Context, name string) error diff --git a/pkg/client/deferred.go b/pkg/client/deferred.go index 70e2a3afb..175939011 100644 --- a/pkg/client/deferred.go +++ b/pkg/client/deferred.go @@ -73,6 +73,13 @@ func (d *DeferredClient) AppRun(ctx context.Context, image string, opts *AppRunO return d.Client.AppRun(ctx, image, opts) } +func (d *DeferredClient) AppInfo(ctx context.Context, name string) (string, error) { + if err := d.create(); err != nil { + return "", err + } + return d.Client.AppInfo(ctx, name) +} + func (d *DeferredClient) AppUpdate(ctx context.Context, name string, opts *AppUpdateOptions) (*apiv1.App, error) { if err := d.create(); err != nil { return nil, err diff --git a/pkg/client/multi.go b/pkg/client/multi.go index 82f2d3625..658813d3b 100644 --- a/pkg/client/multi.go +++ b/pkg/client/multi.go @@ -149,6 +149,20 @@ func (m *MultiClient) AppStop(ctx context.Context, name string) error { return err } +func (m *MultiClient) AppInfo(ctx context.Context, name string) (string, error) { + var ( + info = "" + err error + ) + + _, err = onOne(ctx, m.Factory, name, func(name string, c Client) (*apiv1.App, error) { + info, err = c.AppInfo(ctx, name) + return &apiv1.App{}, err + }) + + return info, err +} + func (m *MultiClient) AppStart(ctx context.Context, name string) error { _, err := onOne(ctx, m.Factory, name, func(name string, c Client) (*apiv1.App, error) { return &apiv1.App{}, c.AppStart(ctx, name) diff --git a/pkg/controller/appstatus/appstatus.go b/pkg/controller/appstatus/appstatus.go index 35c3a13ed..5e0ca7869 100644 --- a/pkg/controller/appstatus/appstatus.go +++ b/pkg/controller/appstatus/appstatus.go @@ -156,6 +156,12 @@ func setCondition[T commonStatusGetter](obj kclient.Object, conditionName string errorMessages = append(errorMessages, formatMessage(name, status.ErrorMessages, ", ")) } else if len(status.TransitioningMessages) > 0 { transitioningMessages = append(transitioningMessages, formatMessage(name, status.TransitioningMessages, ", ")) + } else if !status.Defined { + transitioningMessages = append(transitioningMessages, formatMessage(name, []string{"pending"}, ", ")) + } else if !status.UpToDate { + transitioningMessages = append(transitioningMessages, formatMessage(name, []string{"updating"}, ", ")) + } else if !status.Ready { + transitioningMessages = append(transitioningMessages, formatMessage(name, []string{"not ready"}, ", ")) } } diff --git a/pkg/mocks/mock_client.go b/pkg/mocks/mock_client.go index c7608c738..f4b7cc837 100644 --- a/pkg/mocks/mock_client.go +++ b/pkg/mocks/mock_client.go @@ -157,6 +157,21 @@ func (mr *MockClientMockRecorder) AppIgnoreDeleteCleanup(arg0, arg1 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppIgnoreDeleteCleanup", reflect.TypeOf((*MockClient)(nil).AppIgnoreDeleteCleanup), arg0, arg1) } +// AppInfo mocks base method. +func (m *MockClient) AppInfo(arg0 context.Context, arg1 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AppInfo", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AppInfo indicates an expected call of AppInfo. +func (mr *MockClientMockRecorder) AppInfo(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppInfo", reflect.TypeOf((*MockClient)(nil).AppInfo), arg0, arg1) +} + // AppList mocks base method. func (m *MockClient) AppList(arg0 context.Context) ([]v1.App, error) { m.ctrl.T.Helper() diff --git a/pkg/server/registry/apigroups/acorn/apps/info.go b/pkg/server/registry/apigroups/acorn/apps/info.go index 3ad935bd5..5676f2d9a 100644 --- a/pkg/server/registry/apigroups/acorn/apps/info.go +++ b/pkg/server/registry/apigroups/acorn/apps/info.go @@ -2,6 +2,7 @@ package apps import ( "context" + "strings" "github.com/acorn-io/mink/pkg/stores" "github.com/acorn-io/mink/pkg/types" @@ -34,31 +35,43 @@ type InfoStrategy struct { func (s *InfoStrategy) Get(ctx context.Context, namespace, name string) (types.Object, error) { ri, _ := request.RequestInfoFrom(ctx) - app := &apiv1.App{} - err := s.client.Get(ctx, kclient.ObjectKey{Namespace: ri.Namespace, Name: ri.Name}, app) - if err != nil { - return nil, err - } + var ( + appInstance = &v1.AppInstance{} + err error + ) - appInstances := &v1.AppInstanceList{} - err = s.client.List(ctx, appInstances, &kclient.ListOptions{ - LabelSelector: klabels.SelectorFromSet(klabels.Set{ - labels.AcornPublicName: name, - }), - Namespace: ri.Namespace, - }) - if err != nil { - return nil, err - } + if strings.Contains(name, ".") { + app := &apiv1.App{} + err := s.client.Get(ctx, kclient.ObjectKey{Namespace: ri.Namespace, Name: ri.Name}, app) + if err != nil { + return nil, err + } - if len(appInstances.Items) != 1 { - return nil, apierrors.NewNotFound(schema.GroupResource{ - Group: apiv1.SchemeGroupVersion.Group, - Resource: "apps", - }, name) - } + appInstances := &v1.AppInstanceList{} + err = s.client.List(ctx, appInstances, &kclient.ListOptions{ + LabelSelector: klabels.SelectorFromSet(klabels.Set{ + labels.AcornPublicName: name, + }), + Namespace: ri.Namespace, + }) + if err != nil { + return nil, err + } - appInstance := &appInstances.Items[0] + if len(appInstances.Items) != 1 { + return nil, apierrors.NewNotFound(schema.GroupResource{ + Group: apiv1.SchemeGroupVersion.Group, + Resource: "apps", + }, name) + } + + appInstance = &appInstances.Items[0] + } else { + err := s.client.Get(ctx, kclient.ObjectKey{Namespace: ri.Namespace, Name: ri.Name}, appInstance) + if err != nil { + return nil, err + } + } resp := &apiv1.AppInfo{ ObjectMeta: metav1.ObjectMeta{