Skip to content

Commit db1f396

Browse files
[ADDED NEW FEATURE FOR PORT ASSIGNMENT]
1 parent 127a97a commit db1f396

File tree

3 files changed

+236
-115
lines changed

3 files changed

+236
-115
lines changed

cmd/generate.go

Lines changed: 215 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -15,160 +15,258 @@ import (
1515
"encoding/json"
1616
)
1717

18+
type CustomError struct {
19+
Err error
20+
}
21+
22+
func (e *CustomError) Error() string {
23+
return e.Err.Error()
24+
}
25+
1826
//go:embed templates/*
1927
var templatesFS embed.FS
2028

21-
type PortSequence struct {
22-
LastPort int `json:"last_port"`
29+
// PortInfo represents a single entry in the used ports file.
30+
type PortInfo struct {
31+
Port int `json:"port"`
32+
Service string `json:"service"`
2333
}
2434

35+
// UsedPorts represents the entire used ports file content.
2536
type UsedPorts struct {
26-
Ports []int `json:"used_ports"`
37+
Ports []PortInfo `json:"used_ports"`
2738
}
2839

40+
// serviceExists checks if a directory for the service already exists.
41+
func serviceExists(serviceName string) bool {
42+
// os.Stat gets file info. If the file doesn't exist, it returns a specific error.
43+
_, err := os.Stat(serviceName)
44+
return !os.IsNotExist(err)
45+
}
46+
47+
// readUsedPorts reads the used ports from a JSON file.
2948
func readUsedPorts(filename string) (*UsedPorts, error) {
30-
data := &UsedPorts{}
49+
data := &UsedPorts{}
3150

32-
content, err := ioutil.ReadFile(filename)
33-
if err != nil {
34-
if os.IsNotExist(err) {
35-
// File doesn't exist, return empty UsedPorts
36-
return data, nil
37-
}
38-
return nil, err
39-
}
40-
41-
err = json.Unmarshal(content, data)
42-
if err != nil {
43-
return nil, err
44-
}
45-
return data, nil
51+
content, err := ioutil.ReadFile(filename)
52+
if err != nil {
53+
if os.IsNotExist(err) {
54+
// File doesn't exist, return empty UsedPorts
55+
return data, nil
56+
}
57+
return nil, err
58+
}
59+
60+
err = json.Unmarshal(content, data)
61+
if err != nil {
62+
return nil, err
63+
}
64+
return data, nil
4665
}
4766

67+
// writeUsedPorts writes the used ports data to a JSON file.
4868
func writeUsedPorts(filename string, data *UsedPorts) error {
49-
bytes, err := json.MarshalIndent(data, "", " ")
50-
if err != nil {
51-
return err
52-
}
53-
return ioutil.WriteFile(filename, bytes, 0644)
69+
bytes, err := json.MarshalIndent(data, "", " ")
70+
if err != nil {
71+
return err
72+
}
73+
return ioutil.WriteFile(filename, bytes, 0644)
5474
}
5575

56-
// Check if port is in UsedPorts.Ports slice
76+
// isPortUsed checks if a port is in the UsedPorts slice.
5777
func isPortUsed(used *UsedPorts, port int) bool {
58-
for _, p := range used.Ports {
59-
if p == port {
60-
return true
61-
}
62-
}
63-
return false
78+
for _, p := range used.Ports {
79+
if p.Port == port {
80+
return true
81+
}
82+
}
83+
return false
6484
}
6585

66-
// Find next available port, skipping used ports and occupied OS ports
86+
// getNextAvailablePort finds the next available port, skipping used and occupied ports.
6787
func getNextAvailablePort(start int, used *UsedPorts) (int, error) {
68-
for port := start; port <= 65535; port++ {
69-
if isPortUsed(used, port) {
70-
continue
71-
}
88+
for port := start; port <= 65535; port++ {
89+
if isPortUsed(used, port) {
90+
continue
91+
}
7292

73-
addr := fmt.Sprintf(":%d", port)
74-
l, err := net.Listen("tcp", addr)
75-
if err != nil {
76-
// Port in use at OS level, skip
77-
continue
78-
}
79-
l.Close()
80-
return port, nil
81-
}
82-
return 0, fmt.Errorf("no available port found starting at %d", start)
93+
addr := fmt.Sprintf(":%d", port)
94+
l, err := net.Listen("tcp", addr)
95+
if err != nil {
96+
// Port in use at OS level, skip
97+
continue
98+
}
99+
l.Close()
100+
return port, nil
101+
}
102+
return 0, fmt.Errorf("no available port found starting at %d", start)
83103
}
84104

85-
func readAndIncrementPortWithUsed(start int, nextPortFile, usedPortsFile string) (int, error) {
86-
// Read next port number
87-
nextPort := start
88-
portBytes, err := ioutil.ReadFile(nextPortFile)
89-
if err == nil {
90-
p, err := strconv.Atoi(string(portBytes))
91-
if err == nil && p >= start {
92-
nextPort = p
93-
}
94-
}
95-
96-
// Read used ports slice
97-
usedPorts, err := readUsedPorts(usedPortsFile)
98-
if err != nil {
99-
return 0, err
100-
}
101-
102-
// Get next available port skipping used
103-
port, err := getNextAvailablePort(nextPort, usedPorts)
104-
if err != nil {
105-
return 0, err
106-
}
107-
108-
// Add new port to used ports slice
109-
usedPorts.Ports = append(usedPorts.Ports, port)
110-
111-
// Write back updated used ports
112-
err = writeUsedPorts(usedPortsFile, usedPorts)
113-
if err != nil {
114-
return 0, err
115-
}
116-
117-
// Update next port file to port+1
118-
err = ioutil.WriteFile(nextPortFile, []byte(strconv.Itoa(port+1)), 0644)
119-
if err != nil {
120-
return 0, err
121-
}
122-
123-
return port, nil
105+
// readAndIncrementPortWithUsed finds the next available port and stores it.
106+
func readAndIncrementPortWithUsed(start int, serviceName, nextPortFile, usedPortsFile string) (int, error) {
107+
// Read next port number
108+
nextPort := start
109+
portBytes, err := ioutil.ReadFile(nextPortFile)
110+
if err == nil {
111+
p, err := strconv.Atoi(string(portBytes))
112+
if err == nil && p >= start {
113+
nextPort = p
114+
}
115+
}
116+
117+
// Read used ports slice
118+
usedPorts, err := readUsedPorts(usedPortsFile)
119+
if err != nil {
120+
return 0, err
121+
}
122+
123+
// Get next available port skipping used
124+
port, err := getNextAvailablePort(nextPort, usedPorts)
125+
if err != nil {
126+
return 0, err
127+
}
128+
129+
// Add new port to used ports slice with the service name
130+
usedPorts.Ports = append(usedPorts.Ports, PortInfo{Port: port, Service: serviceName})
131+
132+
// Write back updated used ports
133+
err = writeUsedPorts(usedPortsFile, usedPorts)
134+
if err != nil {
135+
return 0, err
136+
}
137+
138+
// Update next port file to port+1
139+
err = ioutil.WriteFile(nextPortFile, []byte(strconv.Itoa(port+1)), 0644)
140+
if err != nil {
141+
return 0, err
142+
}
143+
144+
return port, nil
145+
}
146+
147+
// writeUsedPortForService adds a user-provided port and service to the used ports file.
148+
func writeUsedPortForService(port int, serviceName, filename string) error {
149+
usedPorts, err := readUsedPorts(filename)
150+
if err != nil {
151+
return err
152+
}
153+
154+
// Check if the port is already in the list
155+
if !isPortUsed(usedPorts, port) {
156+
usedPorts.Ports = append(usedPorts.Ports, PortInfo{Port: port, Service: serviceName})
157+
return writeUsedPorts(filename, usedPorts)
158+
}
159+
160+
// Port is already in the list, no need to write.
161+
return nil
124162
}
125163

126164
var generateCmd = &cobra.Command{
127165
Use: "generate [service-name] [port]",
128166
Short: "Generate code resources",
129167
Long: "Generate microservice boilerplate code including router, controller, service, entity, go.mod, Dockerfile, and go.sum.",
130-
Args: func(cmd *cobra.Command, args []string) error {
131-
if len(args) < 1 {
132-
return fmt.Errorf("requires service name argument")
133-
}
134-
if len(args) > 1 {
135-
port := args[1]
136-
if len(port) == 0 {
137-
return fmt.Errorf("port cannot be empty string")
138-
}
139-
p, err := strconv.Atoi(port)
140-
if err != nil {
141-
return fmt.Errorf("port must be a valid number")
142-
}
143-
if p < 1024 || p > 65535 {
144-
return fmt.Errorf("port must be between 1024 and 65535")
168+
Args: func(cmd *cobra.Command, args []string) error {
169+
if len(args) < 1 {
170+
return fmt.Errorf("requires service name argument")
171+
}
172+
if len(args) > 1 {
173+
port := args[1]
174+
if len(port) == 0 {
175+
return fmt.Errorf("port cannot be empty string")
176+
}
177+
p, err := strconv.Atoi(port)
178+
if err != nil {
179+
return fmt.Errorf("port must be a valid number")
180+
}
181+
if p < 1024 || p > 65535 {
182+
return fmt.Errorf("port must be between 1024 and 65535")
183+
}
184+
}
185+
return nil
186+
},
187+
RunE: func(cmd *cobra.Command, args []string) error {
188+
serviceName := args[0]
189+
usedPortsFile := "used_ports.json"
190+
191+
// 1. Check if the service name already exists.
192+
// Assuming you have a function to check for existing services.
193+
if serviceExists(serviceName) {
194+
return fmt.Errorf("a service with the name '%s' already exists", serviceName)
195+
}
196+
197+
var port int
198+
if len(args) > 1 && args[1] != "" {
199+
var err error
200+
port, err = strconv.Atoi(args[1])
201+
if err != nil {
202+
return fmt.Errorf("port must be a valid number: %w", err)
203+
}
204+
205+
// 2. Check if the user-provided port is already used.
206+
usedPorts, err := readUsedPorts(usedPortsFile)
207+
if err != nil {
208+
return fmt.Errorf("failed to read used ports file: %w", err)
209+
}
210+
if isPortUsed(usedPorts, port) {
211+
return fmt.Errorf("the port '%d' is already in use", port)
212+
}
213+
214+
// Store the user-provided port with the service name.
215+
err = writeUsedPortForService(port, serviceName, usedPortsFile)
216+
if err != nil {
217+
return fmt.Errorf("failed to store user-provided port: %w", err)
218+
}
219+
220+
} else {
221+
// Logic for automatically finding a port remains the same.
222+
portFile := "port.json"
223+
p, err := readAndIncrementPortWithUsed(8080, serviceName, portFile, usedPortsFile)
224+
if err != nil {
225+
return fmt.Errorf("failed to get next available port: %w", err)
226+
}
227+
port = p
228+
}
229+
230+
return generateService(serviceName, strconv.Itoa(port))
231+
},
232+
}
233+
234+
// listServicesCmd is a new Cobra command to list all services and their ports.
235+
var listServicesCmd = &cobra.Command{
236+
Use: "list-services",
237+
Short: "List all generated services and their ports",
238+
Long: "Displays a list of all microservices that have been generated, along with the ports they are using.",
239+
Run: func(cmd *cobra.Command, args []string) {
240+
usedPortsFile := "used_ports.json"
241+
242+
usedPorts, err := readUsedPorts(usedPortsFile)
243+
if err != nil {
244+
// Check if the error is due to the file not existing.
245+
if os.IsNotExist(err) {
246+
fmt.Println("No services have been generated yet")
247+
return
145248
}
249+
// For other file-related errors, print a more detailed message.
250+
fmt.Fprintf(os.Stderr, "Error reading used ports file: %v\n", err)
251+
return
146252
}
147-
return nil
148-
},
149-
RunE: func(cmd *cobra.Command, args []string) error {
150-
serviceName := args[0]
151253

152-
var port string
153-
if len(args) > 1 && args[1] != "" {
154-
port = args[1]
254+
if len(usedPorts.Ports) == 0 {
255+
fmt.Println("No services have been generated yet.")
155256
} else {
156-
portFile := "port.json"
157-
usedPortsFile := "used_ports.json"
158-
159-
p, err := readAndIncrementPortWithUsed(8080, portFile, usedPortsFile)
160-
if err != nil {
161-
return fmt.Errorf("failed to get next available port: %w", err)
257+
fmt.Println("--- Generated Services ---")
258+
for _, pInfo := range usedPorts.Ports {
259+
fmt.Printf("Service: %-25s Port: %d\n", pInfo.Service, pInfo.Port)
162260
}
163-
port = strconv.Itoa(p)
261+
fmt.Println("--------------------------")
164262
}
165-
return generateService(serviceName, port) // Your Restful generator
166-
167263
},
168264
}
169265

170266
func init() {
171267
rootCmd.AddCommand(generateCmd)
268+
269+
rootCmd.AddCommand(listServicesCmd)
172270
}
173271

174272
func generateService(name, port string) error {
@@ -330,6 +428,7 @@ func createMicroservice(name, port string) error {
330428
"service.tmpl",
331429
"go.mod.tmpl",
332430
"Dockerfile.tmpl",
431+
"entity_pkg.tmpl", // New template for the entity
333432
}
334433

335434
outputFiles := []string{
@@ -339,6 +438,7 @@ func createMicroservice(name, port string) error {
339438
filepath.Join(basePath, "internal", "service.go"),
340439
filepath.Join(basePath, "go.mod"),
341440
filepath.Join(basePath, "Dockerfile"),
441+
filepath.Join("pkg", "entities", fmt.Sprintf("%s.entity.go", name)),
342442
}
343443

344444
funcMap := template.FuncMap{

port.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8084

0 commit comments

Comments
 (0)