Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions agent.codefly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ version: 0.0.18
release:
owner: workflow
workflow: releaser.yml
conformance:
mode: attach-existing-source
fixture: testdata/workspace
11 changes: 11 additions & 0 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ func (s *Builder) Init(ctx context.Context, req *builderv0.InitRequest) (*builde
return s.Builder.InitResponse()
}

// Temporal runs an upstream image and owns no generated source files. Both sync
// modes therefore leave the loaded service unchanged, with no drift to report.
func (s *Builder) Sync(context.Context, *builderv0.SyncRequest) (*builderv0.SyncResponse, error) {
return s.Builder.SyncResponse()
}

func (s *Builder) Build(context.Context, *builderv0.BuildRequest) (*builderv0.BuildResponse, error) {
s.Builder.WithDockerImages(image)
return s.Builder.BuildResponse()
}

func (s *Builder) Audit(ctx context.Context, req *builderv0.AuditRequest) (*builderv0.AuditResponse, error) {
defer s.Wool.Catch()
ctx = s.Wool.Inject(ctx)
Expand Down
33 changes: 33 additions & 0 deletions conformance_fixture_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"testing"

"github.com/codefly-dev/core/resources"
)

func TestConformanceFixtureLoadsCandidateWithPostgresDependency(t *testing.T) {
workspace, err := resources.LoadWorkspaceFromDir(t.Context(), "testdata/workspace")
if err != nil {
t.Fatal(err)
}
services, err := workspace.LoadServices(t.Context())
if err != nil {
t.Fatal(err)
}
var candidate, database *resources.Service
for _, service := range services {
switch service.Name {
case "temporal":
candidate = service
case "postgres":
database = service
}
}
if candidate == nil || database == nil || candidate.Agent.Version != "latest" || database.Agent.Version != "0.0.134" {
t.Fatal("fixture must qualify the candidate against its declared published database agent")
}
if len(candidate.ServiceDependencies) != 1 || candidate.ServiceDependencies[0].Name != database.Name {
t.Fatal("Temporal conformance must include its actual persistence dependency")
}
}
17 changes: 16 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,24 @@ func (s *Service) GetAgentInformation(ctx context.Context, _ *agentv0.AgentInfor
Backends: runnersbase.BackendSupport{
Local: func() bool { return languages.HasGoRuntime(nil) },
Nix: false,
Docker: true,
Docker: false,
},
Toolchains: []agentv0.Toolchain_Type{agentv0.Toolchain_GO},
Validation: &agentv0.ValidationCapabilities{
Test: &agentv0.TestValidationCapability{
Supported: true,
Scopes: []agentv0.ValidationScope{agentv0.ValidationScope_VALIDATION_SCOPE_WORKSPACE},
Suites: []*agentv0.TestSuiteCapability{{
Name: "readiness",
DefaultSuite: true,
DependencyMode: agentv0.TestDependencyMode_TEST_DEPENDENCY_MODE_START_STACK,
}},
},
Sync: &agentv0.ValidationOperationCapability{Supported: true, Scopes: []agentv0.ValidationScope{agentv0.ValidationScope_VALIDATION_SCOPE_WORKSPACE}},
ArtifactBuild: &agentv0.ValidationOperationCapability{Supported: true, Scopes: []agentv0.ValidationScope{agentv0.ValidationScope_VALIDATION_SCOPE_WORKSPACE}},
Audit: &agentv0.ValidationOperationCapability{Supported: true},
Sbom: &agentv0.ValidationOperationCapability{Supported: true},
},
Config: []*agentv0.ConfigurationValueDetail{
{
Name: "connection", Description: "Temporal connection details",
Expand Down
5 changes: 5 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ func bootTemporal(ctx context.Context, t *testing.T, pgConn string) string {
_, err = runtime.Start(ctx, &runtimev0.StartRequest{})
require.NoError(t, err)
t.Logf("Temporal running on port %d", runtime.grpcPort)
readiness, err := runtime.Test(ctx, &runtimev0.TestRequest{Suite: "readiness"})
require.NoError(t, err)
require.Equal(t, runtimev0.TestStatus_SUCCESS, readiness.GetStatus().GetState())
require.EqualValues(t, 1, readiness.GetTestsRun())
require.EqualValues(t, 1, readiness.GetTestsPassed())

// Extract gRPC connection
temporalConf, err := resources.ExtractConfiguration(temporalInitResp.RuntimeConfigurations, resources.NewRuntimeContextNative())
Expand Down
74 changes: 74 additions & 0 deletions managed_image_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package main

import (
"io/fs"
"os"
"testing"

basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0"
agentv0 "github.com/codefly-dev/core/generated/go/codefly/services/agent/v0"
builderv0 "github.com/codefly-dev/core/generated/go/codefly/services/builder/v0"
"github.com/stretchr/testify/require"
)

func TestManagedImageSyncAndBuild(t *testing.T) {
root := t.TempDir()
require.NoError(t, os.CopyFS(root, os.DirFS("testdata/workspace")))
builder := NewBuilder()
loaded, err := builder.Load(t.Context(), &builderv0.LoadRequest{
Identity: &basev0.ServiceIdentity{
Name: "temporal", Version: "0.0.0", Module: "temporal-test",
Workspace: "temporal-test", WorkspacePath: root,
RelativeToWorkspace: "services/temporal",
},
})
require.NoError(t, err)
require.Equal(t, builderv0.LoadStatus_READY, loaded.GetState().GetState())
before := snapshotFiles(t, root)
for _, dryRun := range []bool{true, false} {
response, err := builder.Sync(t.Context(), &builderv0.SyncRequest{DryRun: dryRun})
require.NoError(t, err)
require.Equal(t, builderv0.SyncStatus_SUCCESS, response.GetState().GetState())
require.Empty(t, response.GetChangedFiles())
require.Equal(t, before, snapshotFiles(t, root))
}
response, err := builder.Build(t.Context(), &builderv0.BuildRequest{})
require.NoError(t, err)
require.Equal(t, builderv0.BuildStatus_SUCCESS, response.GetState().GetState())
require.Equal(t, []string{image.FullName()}, response.GetResult().GetDockerBuildResult().GetImages())
require.NotEmpty(t, image.Digest)
require.Equal(t, before, snapshotFiles(t, root))

info, err := builder.GetAgentInformation(t.Context(), &agentv0.AgentInformationRequest{})
require.NoError(t, err)
require.True(t, info.GetValidation().GetSync().GetSupported())
require.True(t, info.GetValidation().GetArtifactBuild().GetSupported())
// There is no native source to lint or compile in this managed-image service.
require.False(t, info.GetValidation().GetLint().GetSupported())
require.False(t, info.GetValidation().GetCompile().GetSupported())
}

func TestManagedImageOperationsRequireLoadedService(t *testing.T) {
builder := NewBuilder()
sync, err := builder.Sync(t.Context(), &builderv0.SyncRequest{DryRun: true})
require.NoError(t, err)
require.Equal(t, builderv0.SyncStatus_ERROR, sync.GetState().GetState())
build, err := builder.Build(t.Context(), &builderv0.BuildRequest{})
require.NoError(t, err)
require.Equal(t, builderv0.BuildStatus_ERROR, build.GetState().GetState())
}

func snapshotFiles(t *testing.T, root string) map[string]string {
t.Helper()
files := map[string]string{}
filesystem := os.DirFS(root)
require.NoError(t, fs.WalkDir(filesystem, ".", func(path string, entry fs.DirEntry, err error) error {
if err != nil || entry.IsDir() {
return err
}
contents, err := fs.ReadFile(filesystem, path)
files[path] = string(contents)
return err
}))
return files
}
42 changes: 42 additions & 0 deletions readiness_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"testing"

agentv0 "github.com/codefly-dev/core/generated/go/codefly/services/agent/v0"
runtimev0 "github.com/codefly-dev/core/generated/go/codefly/services/runtime/v0"
"github.com/stretchr/testify/require"
)

func TestReadinessAdvertisesItsLiveStack(t *testing.T) {
info, err := NewService().GetAgentInformation(t.Context(), &agentv0.AgentInformationRequest{})
require.NoError(t, err)
tests := info.GetValidation().GetTest()
require.True(t, tests.GetSupported())
require.Len(t, tests.GetSuites(), 1)
suite := tests.GetSuites()[0]
require.Equal(t, "readiness", suite.GetName())
require.True(t, suite.GetDefaultSuite())
require.Equal(t, agentv0.TestDependencyMode_TEST_DEPENDENCY_MODE_START_STACK, suite.GetDependencyMode())
}

func TestReadinessCannotPassWithoutRunningStack(t *testing.T) {
response, err := NewRuntime().Test(t.Context(), &runtimev0.TestRequest{})
require.NoError(t, err)
require.Equal(t, runtimev0.TestStatus_ERROR, response.GetStatus().GetState())
require.Contains(t, response.GetStatus().GetMessage(), "started service stack")
}

func TestReadinessRejectsUnsupportedRequests(t *testing.T) {
for _, request := range []*runtimev0.TestRequest{
{Suite: "missing"}, {Target: "./..."}, {Filters: []string{"test"}},
{Timeout: "invalid"}, {Timeout: "0s"}, {Race: true}, {Coverage: true},
{ExtraArgs: []string{"--flag"}}, {Formula: &runtimev0.TestFormula{}},
{Selection: &runtimev0.TestSelection{Scope: &runtimev0.TestSelection_Suite{Suite: &runtimev0.TestSuiteSelection{Name: "missing"}}}},
} {
response, err := NewRuntime().Test(t.Context(), request)
require.NoError(t, err)
require.Equal(t, runtimev0.TestStatus_ERROR, response.GetStatus().GetState())
require.NotContains(t, response.GetStatus().GetMessage(), "started service stack")
}
}
41 changes: 35 additions & 6 deletions runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ func (s *Runtime) CreateConnectionsConfiguration(runtimeContext *basev0.RuntimeC
}
}

// resolvePostgresFromDependencies extracts Postgres connection details from
// codefly dependency configurations (postgres service).
// The embedded server executes in the agent process, not in a service container.
// Select the host projection before inspecting the database's declarations.
func (s *Runtime) resolvePostgresFromDependencies(confs []*basev0.Configuration) error {
for _, conf := range confs {
for _, conf := range resources.FilterConfigurations(confs, resources.NewRuntimeContextNative()) {
for _, info := range conf.Infos {
if info.Name == "postgres" {
for _, cv := range info.ConfigurationValues {
Expand Down Expand Up @@ -186,8 +186,7 @@ func (s *Runtime) Init(ctx context.Context, req *runtimev0.InitRequest) (*runtim
s.Infof("Temporal gRPC will run on localhost:%d", s.grpcPort)
s.Infof("Temporal HTTP will run on localhost:%d", s.httpPort)

// Resolve Postgres connection from dependency configurations
// Filter for the appropriate runtime context (like go-grpc does)
// Resolve the host-side Postgres configuration for the embedded server.
if req.DependenciesConfigurations == nil || len(req.DependenciesConfigurations) == 0 {
return s.Runtime.InitErrorf(fmt.Errorf("no dependencies configurations"), "temporal requires postgres dependency")
}
Expand Down Expand Up @@ -467,7 +466,37 @@ func (s *Runtime) Destroy(ctx context.Context, req *runtimev0.DestroyRequest) (*
}

func (s *Runtime) Test(ctx context.Context, req *runtimev0.TestRequest) (*runtimev0.TestResponse, error) {
return s.Runtime.TestResponse()
if req.GetSuite() != "" && req.GetSuite() != "readiness" {
return s.Runtime.TestError(fmt.Errorf("unknown Temporal test suite %q", req.GetSuite()))
}
if selection := req.GetSelection(); selection != nil && selection.GetSuite().GetName() != "readiness" {
return s.Runtime.TestError(fmt.Errorf("Temporal tests support only the readiness suite"))
}
if req.GetTarget() != "" || len(req.GetFilters()) != 0 || len(req.GetExtraArgs()) != 0 || req.GetFormula() != nil || req.GetRace() || req.GetCoverage() {
return s.Runtime.TestError(fmt.Errorf("Temporal readiness does not support source-test selectors or instrumentation"))
}
timeout := 30 * time.Second
if req.GetTimeout() != "" {
var err error
timeout, err = time.ParseDuration(req.GetTimeout())
if err != nil || timeout <= 0 {
return s.Runtime.TestError(fmt.Errorf("invalid readiness timeout %q", req.GetTimeout()))
}
}
if s.temporalServer == nil || s.grpcPort == 0 {
return s.Runtime.TestError(fmt.Errorf("Temporal readiness requires the started service stack"))
}
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
conn, err := grpc.NewClient(fmt.Sprintf("localhost:%d", s.grpcPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err == nil {
defer conn.Close()
_, err = workflowservice.NewWorkflowServiceClient(conn).GetSystemInfo(ctx, &workflowservice.GetSystemInfoRequest{})
}
if err != nil {
return s.Runtime.TestResponseWithResults(1, 0, 1, 0, 0, []string{err.Error()}, err)
}
return s.Runtime.TestResponseWithResults(1, 1, 0, 0, 0, nil, nil)
}

// parsePostgresConnectionString extracts host, port, user, password from a
Expand Down
36 changes: 36 additions & 0 deletions runtime_configuration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"testing"

basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0"
agentv0 "github.com/codefly-dev/core/generated/go/codefly/services/agent/v0"
"github.com/codefly-dev/core/resources"
"github.com/stretchr/testify/require"
)

func TestEmbeddedRuntimeSelectsNativeDatabaseConfiguration(t *testing.T) {
configuration := func(runtime *basev0.RuntimeContext, connection string) *basev0.Configuration {
return &basev0.Configuration{RuntimeContext: runtime, Infos: []*basev0.ConfigurationInformation{{
Name: "postgres", ConfigurationValues: []*basev0.ConfigurationValue{{Key: "owner-connection", Value: connection, Secret: true}},
}}}
}
container := configuration(resources.NewRuntimeContextContainer(), "postgresql://container:wrong@host.docker.internal:6432/postgres")
native := configuration(resources.NewRuntimeContextNative(), "postgresql://native:correct@localhost:5433/postgres")
for _, configs := range [][]*basev0.Configuration{{container, native}, {native, container}} {
runtime := NewRuntime()
require.NoError(t, runtime.resolvePostgresFromDependencies(configs))
require.Equal(t, "localhost", runtime.postgresHost)
require.Equal(t, 5433, runtime.postgresPort)
require.Equal(t, "native", runtime.postgresUser)
require.Equal(t, "correct", runtime.postgresPassword)
}
for _, configs := range [][]*basev0.Configuration{nil, {container}, {nil, container}} {
require.Error(t, NewRuntime().resolvePostgresFromDependencies(configs), "a container-only projection must not become a host connection")
}
info, err := NewService().GetAgentInformation(t.Context(), &agentv0.AgentInformationRequest{})
require.NoError(t, err)
for _, backend := range info.GetSupportedBackends() {
require.NotEqual(t, agentv0.Backend_DOCKER, backend.GetType(), "deployment's managed image is not an implemented runtime backend")
}
}
2 changes: 1 addition & 1 deletion testdata/workspace/services/postgres/service.codefly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 0.0.0
agent:
kind: codefly:service
name: postgres
version: 0.0.133
version: 0.0.134
publisher: codefly.dev
endpoints:
- name: tcp
Expand Down
2 changes: 1 addition & 1 deletion testdata/workspace/services/temporal/service.codefly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 0.0.0
agent:
kind: codefly:service
name: temporal
version: 0.0.17
version: latest
publisher: codefly.dev
service-dependencies:
- name: postgres
Expand Down
Loading