Skip to content

Commit 5ac012c

Browse files
authored
Example service template for devenv (#2365)
example service template
1 parent 8bc75b5 commit 5ac012c

File tree

5 files changed

+179
-1
lines changed

5 files changed

+179
-1
lines changed

framework/.changeset/v0.13.11.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Devenv service generated template

framework/cmd/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ Usage:
9797
if err := cg.Write(); err != nil {
9898
return fmt.Errorf("failed to generate environment: %w", err)
9999
}
100+
if err := cg.WriteServices(); err != nil {
101+
return fmt.Errorf("failed to generate services: %w", err)
102+
}
100103
if err := cg.WriteFakes(); err != nil {
101104
return fmt.Errorf("failed to generate fakes: %w", err)
102105
}

framework/tmpl_gen_env.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ just cli && {{ .CLIName }} sh
3737
3838
## Functional Testing
3939
40-
🚀 Implement your product configuration (see comnents in [configurator.go](./products/productone/configurator.go))
40+
🔍 Implement your product configuration (see comnents in [configurator.go](./products/productone/configurator.go))
41+
42+
Add any additional product services using this [template](./services/svc.go)
4143
4244
Start the environment.
4345
` + "```" + `bash
@@ -933,6 +935,10 @@ instances = 1
933935
image = "{{ .ProductName }}-fakes:latest"
934936
port = 9111
935937
938+
[example_service]
939+
image = "busybox:latest"
940+
port = 9501
941+
936942
[[blockchains]]
937943
chain_id = "1337"
938944
docker_cmd_params = ["-b", "1", "--mixed-mining", "--slots-in-an-epoch", "1"]
@@ -1992,6 +1998,7 @@ import (
19921998
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/fake"
19931999
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/jd"
19942000
"github.com/smartcontractkit/{{ .ProductName }}/devenv/products/{{ .ProductName }}"
2001+
"github.com/smartcontractkit/{{ .ProductName }}/devenv/services"
19952002
19962003
ns "github.com/smartcontractkit/chainlink-testing-framework/framework/components/simple_node_set"
19972004
)
@@ -2005,6 +2012,7 @@ type ProductInfo struct {
20052012
20062013
type Cfg struct {
20072014
Products []*ProductInfo ` + "`" + `toml:"products"` + "`" + `
2015+
Service *services.ExampleSvcInput ` + "`" + `toml:"example_service"` + "`" + `
20082016
Blockchains []*blockchain.Input ` + "`" + `toml:"blockchains" validate:"required"` + "`" + `
20092017
FakeServer *fake.Input ` + "`" + `toml:"fake_server" validate:"required"` + "`" + `
20102018
NodeSets []*ns.Input ` + "`" + `toml:"nodesets" validate:"required"` + "`" + `
@@ -2042,6 +2050,10 @@ func NewEnvironment(ctx context.Context) error {
20422050
return fmt.Errorf("failed to create fake data provider: %w", err)
20432051
}
20442052
2053+
if err := services.NewService(in.Service); err != nil {
2054+
return fmt.Errorf("failed to create example service: %w", err)
2055+
}
2056+
20452057
// get all the product orchestrations, generate product specific overrides
20462058
productConfigurators := make([]Product, 0)
20472059
nodeConfigs := make([]string, 0)

framework/tmpl_gen_svc.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package framework
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/rs/zerolog/log"
9+
)
10+
11+
/* Templates */
12+
13+
const (
14+
ProductComponentImpl = `
15+
package services
16+
17+
/*
18+
* A simple template to add your project services to devenv.
19+
* Each service should have a file with Input/Output struct and a function deploying it.
20+
*/
21+
22+
import (
23+
"context"
24+
"fmt"
25+
"os"
26+
"strconv"
27+
28+
"github.com/rs/zerolog"
29+
"github.com/rs/zerolog/log"
30+
31+
"github.com/docker/docker/api/types/container"
32+
"github.com/docker/go-connections/nat"
33+
"github.com/smartcontractkit/chainlink-testing-framework/framework"
34+
"github.com/testcontainers/testcontainers-go"
35+
)
36+
37+
var L = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.DebugLevel).With().Fields(map[string]any{"component": "datastreams"}).Logger()
38+
39+
const (
40+
DefaultExampleSvcName = "example-service"
41+
DefaultExampleSvcImage = "busybox:latest"
42+
)
43+
44+
// ExampleSvcInput example service input
45+
type ExampleSvcInput struct {
46+
Image string ` + "`" + `toml:"image"` + "`" + `
47+
Port int ` + "`" + `toml:"port"` + "`" + `
48+
ContainerName string ` + "`" + `toml:"container_name"` + "`" + `
49+
EnvVars map[string]string ` + "`" + `toml:"env_vars"` + "`" + `
50+
Out *ExampleSvcOutput ` + "`" + `toml:"out"` + "`" + `
51+
}
52+
53+
// Default is a default input configuration
54+
func (s *ExampleSvcInput) Default() {
55+
if s.Image == "" {
56+
s.Image = DefaultExampleSvcImage
57+
}
58+
if s.Port == 0 {
59+
s.Port = 9501
60+
}
61+
if s.ContainerName == "" {
62+
s.ContainerName = DefaultExampleSvcName
63+
}
64+
}
65+
66+
// ExampleSvcOutput represents service connection details which can be consumed by tests
67+
// or environment.go, or any other metadata your need to expose to clients
68+
type ExampleSvcOutput struct {
69+
ServiceURL string ` + "`" + `toml:"service_url"` + "`" + `
70+
}
71+
72+
// NewService deploys an example service and populates output data
73+
func NewService(in *ExampleSvcInput) error {
74+
if in == nil || in.Out != nil {
75+
// either service key is not present in configuration
76+
// or it is already deployed because we have an output, skipping
77+
L.Info().Str("ServiceName", DefaultExampleSvcName).Msg("service is skipped or already deployed")
78+
return nil
79+
}
80+
ctx := context.Background()
81+
// read and apply default inputs
82+
in.Default()
83+
// create your service container, use static ports
84+
req := testcontainers.ContainerRequest{
85+
Image: in.Image,
86+
Name: in.ContainerName,
87+
Labels: framework.DefaultTCLabels(),
88+
Networks: []string{framework.DefaultNetworkName},
89+
NetworkAliases: map[string][]string{
90+
framework.DefaultNetworkName: {in.ContainerName},
91+
},
92+
Env: in.EnvVars,
93+
Cmd: []string{"sleep", "infinity"},
94+
// add more internal ports here with /tcp suffix, ex.: 9501/tcp
95+
ExposedPorts: []string{"9501/tcp"},
96+
HostConfigModifier: func(h *container.HostConfig) {
97+
h.PortBindings = nat.PortMap{
98+
// add more internal/external pairs here, ex.: 9501/tcp as a key and HostPort is the exposed port (no /tcp prefix!)
99+
"9501/tcp": []nat.PortBinding{
100+
{HostPort: strconv.Itoa(in.Port)},
101+
},
102+
}
103+
},
104+
// for complex services wait for specific log message
105+
// WaitingFor: wait.ForLog("Some log message").
106+
// WithStartupTimeout(120 * time.Second).
107+
// WithPollInterval(3 * time.Second),
108+
}
109+
_, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
110+
ContainerRequest: req,
111+
Started: true,
112+
})
113+
if err != nil {
114+
return fmt.Errorf("failed to setup %s service", in.ContainerName)
115+
}
116+
117+
// write outputs on a shared struct
118+
in.Out = &ExampleSvcOutput{
119+
ServiceURL: "https://example.com",
120+
}
121+
return nil
122+
}
123+
`
124+
)
125+
126+
// SvcImplParams service template params
127+
type SvcImplParams struct{}
128+
129+
// GenerateServiceImpl generates example service implementation
130+
func (g *EnvCodegen) GenerateServiceImpl() (string, error) {
131+
log.Info().Msg("Generating service implementation")
132+
p := SvcImplParams{}
133+
return render(ProductComponentImpl, p)
134+
}
135+
136+
// WriteServices write all things related to example service template
137+
func (g *EnvCodegen) WriteServices() error {
138+
servicesRoot := filepath.Join(g.cfg.outputDir, "services")
139+
if err := os.MkdirAll( //nolint:gosec
140+
servicesRoot,
141+
os.ModePerm,
142+
); err != nil {
143+
return fmt.Errorf("failed to create services directory: %w", err)
144+
}
145+
146+
// generate Docker wrapper for a service
147+
serviceImplContents, err := g.GenerateServiceImpl()
148+
if err != nil {
149+
return err
150+
}
151+
if err := os.WriteFile( //nolint:gosec
152+
filepath.Join(servicesRoot, "svc.go"),
153+
[]byte(serviceImplContents),
154+
os.ModePerm,
155+
); err != nil {
156+
return fmt.Errorf("failed to write service file: %w", err)
157+
}
158+
159+
return nil
160+
}

framework/tmpl_gen_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ func TestSmokeGenerateDevEnv(t *testing.T) {
7575
require.NoError(t, err)
7676
err = cg.Write()
7777
require.NoError(t, err)
78+
err = cg.WriteServices()
79+
require.NoError(t, err)
7880
err = cg.WriteFakes()
7981
require.NoError(t, err)
8082
err = cg.WriteProducts()

0 commit comments

Comments
 (0)