Skip to content

Commit 8db98de

Browse files
authored
Merge pull request #156 from netboxlabs/develop
release 🚚
2 parents 4259667 + 2928693 commit 8db98de

File tree

13 files changed

+665
-1850
lines changed

13 files changed

+665
-1850
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
name: Orb Agent - PR Semantic Release Check
2+
on:
3+
pull_request:
4+
branches: [ release ]
5+
6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.head_ref }}
8+
cancel-in-progress: true
9+
10+
env:
11+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12+
SEMANTIC_RELEASE_PACKAGE: ${{ github.repository }}
13+
APP_NAME: orb-agent
14+
15+
permissions:
16+
contents: read
17+
pull-requests: write
18+
19+
jobs:
20+
semantic-release-dry-run:
21+
name: Semantic Release Dry Run
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 10
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v4
27+
with:
28+
fetch-depth: 0
29+
30+
- name: Setup Node.js
31+
uses: actions/setup-node@v4
32+
with:
33+
node-version: "lts/*"
34+
35+
- name: Write package.json
36+
uses: DamianReeves/write-file-action@6929a9a6d1807689191dcc8bbe62b54d70a32b42 #v1.3
37+
with:
38+
path: ./package.json
39+
write-mode: overwrite
40+
contents: |
41+
{
42+
"name": "${{ env.APP_NAME }}",
43+
"version": "1.0.0",
44+
"devDependencies": {
45+
"semantic-release-export-data": "^1.0.1",
46+
"@semantic-release/changelog": "^6.0.3"
47+
}
48+
}
49+
50+
- name: Write .releaserc.json
51+
uses: DamianReeves/write-file-action@6929a9a6d1807689191dcc8bbe62b54d70a32b42 #v1.3
52+
with:
53+
path: ./.releaserc.json
54+
write-mode: overwrite
55+
contents: |
56+
{
57+
"branches": "release",
58+
"repositoryUrl": "https://github.com/netboxlabs/orb-agent",
59+
"debug": "true",
60+
"tagFormat": "v${version}",
61+
"plugins": [
62+
["semantic-release-export-data"],
63+
["@semantic-release/commit-analyzer", {
64+
"releaseRules": [
65+
{ "message": "*", "release": "patch"},
66+
{ "message": "fix*", "release": "patch" },
67+
{ "message": "feat*", "release": "minor" },
68+
{ "message": "major*", "release": "major" }
69+
]
70+
}],
71+
"@semantic-release/release-notes-generator",
72+
[
73+
"@semantic-release/changelog",
74+
{
75+
"changelogFile": "CHANGELOG.md",
76+
"changelogTitle": "# Semantic Versioning Changelog"
77+
}
78+
],
79+
[
80+
"@semantic-release/github",
81+
{
82+
"assets": [
83+
{
84+
"path": "release/**"
85+
}
86+
]
87+
}
88+
]
89+
]
90+
}
91+
92+
- name: Install dependencies
93+
run: npm i
94+
95+
- name: Run semantic-release dry-run
96+
id: semantic-release
97+
run: npx semantic-release --debug --dry-run
98+
99+
- name: Comment PR with results
100+
uses: actions/github-script@v7
101+
with:
102+
script: |
103+
const { data: commits } = await github.rest.pulls.listCommits({
104+
owner: context.repo.owner,
105+
repo: context.repo.repo,
106+
pull_number: context.issue.number
107+
});
108+
109+
const commitMessages = commits.map(commit => commit.commit.message).join('\n');
110+
111+
let comment = `## Semantic Release Dry Run Results 🔍\n\n`;
112+
113+
if (process.env.NEW_RELEASE_PUBLISHED === 'true') {
114+
comment += `✅ **A new release would be published!**\n\n`;
115+
comment += `**Version:** ${process.env.NEW_RELEASE_VERSION}\n\n`;
116+
comment += `**Commit Messages:**\n\`\`\`\n${commitMessages}\n\`\`\`\n\n`;
117+
comment += `This PR would trigger a ${process.env.NEW_RELEASE_TYPE || 'patch'} release.`;
118+
} else {
119+
comment += `ℹ️ **No new release would be published**\n\n`;
120+
comment += `**Commit Messages:**\n\`\`\`\n${commitMessages}\n\`\`\`\n\n`;
121+
comment += `No semantic changes detected that would trigger a release.`;
122+
}
123+
124+
await github.rest.issues.createComment({
125+
owner: context.repo.owner,
126+
repo: context.repo.repo,
127+
issue_number: context.issue.number,
128+
body: comment
129+
});
130+
env:
131+
NEW_RELEASE_PUBLISHED: ${{ steps.semantic-release.outputs.new-release-published }}
132+
NEW_RELEASE_VERSION: ${{ steps.semantic-release.outputs.new-release-version }}
133+
NEW_RELEASE_TYPE: ${{ steps.semantic-release.outputs.new-release-type }}

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ A special `common` subsection under `backends` defines configuration settings th
7878
client_id: ${DIODE_CLIENT_ID}
7979
client_secret: ${DIODE_CLIENT_SECRET}
8080
agent_name: agent01
81+
dry_run: false
82+
dry_run_output_dir: /opt/orb
8183
```
8284
8385
### Policies

agent/backend/networkdiscovery/network_discovery.go

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ type networkDiscoveryBackend struct {
3939
apiPort string
4040
apiProtocol string
4141

42-
diodeTarget string
43-
diodeClientID string
44-
diodeClientSecret string
45-
diodeAppNamePrefix string
46-
diodeOtelEndpoint string
42+
diodeTarget string
43+
diodeClientID string
44+
diodeClientSecret string
45+
diodeAppNamePrefix string
46+
diodeOtelEndpoint string
47+
diodeDryRun bool
48+
diodeDryRunOutputDir string
4749

4850
startTime time.Time
4951
proc backend.Commander
@@ -84,6 +86,27 @@ func (d *networkDiscoveryBackend) Configure(logger *slog.Logger, repo policies.P
8486
d.diodeClientID = common.Diode.ClientID
8587
d.diodeClientSecret = common.Diode.ClientSecret
8688
d.diodeAppNamePrefix = common.Diode.AgentName
89+
d.diodeDryRun = common.Diode.DryRun
90+
d.diodeDryRunOutputDir = common.Diode.DryRunOutputDir
91+
92+
if target, prs := config["target"].(string); prs {
93+
d.diodeTarget = target
94+
}
95+
if clientID, prs := config["client_id"].(string); prs {
96+
d.diodeClientID = clientID
97+
}
98+
if clientSecret, prs := config["client_secret"].(string); prs {
99+
d.diodeClientSecret = clientSecret
100+
}
101+
if agentName, prs := config["agent_name"].(string); prs {
102+
d.diodeAppNamePrefix = agentName
103+
}
104+
if dryRun, prs := config["dry_run"].(bool); prs {
105+
d.diodeDryRun = dryRun
106+
}
107+
if dryRunOutputDir, prs := config["dry_run_output_dir"].(string); prs {
108+
d.diodeDryRunOutputDir = dryRunOutputDir
109+
}
87110

88111
if common.Otel.Grpc != "" {
89112
d.diodeOtelEndpoint = common.Otel.Grpc
@@ -110,13 +133,22 @@ func (d *networkDiscoveryBackend) Start(ctx context.Context, cancelFunc context.
110133
d.cancelFunc = cancelFunc
111134
d.ctx = ctx
112135

113-
pvOptions := []string{
114-
"--host", d.apiHost,
115-
"--port", d.apiPort,
116-
"--diode-target", d.diodeTarget,
117-
"--diode-client-id", d.diodeClientID,
118-
"--diode-client-secret", "********",
119-
"--diode-app-name-prefix", d.diodeAppNamePrefix,
136+
var pvOptions []string
137+
if d.diodeDryRun {
138+
pvOptions = []string{
139+
"--dry-run",
140+
"--dry-run-output-dir", d.diodeDryRunOutputDir,
141+
"--diode-app-name-prefix", d.diodeAppNamePrefix,
142+
}
143+
} else {
144+
pvOptions = []string{
145+
"--host", d.apiHost,
146+
"--port", d.apiPort,
147+
"--diode-target", d.diodeTarget,
148+
"--diode-client-id", d.diodeClientID,
149+
"--diode-client-secret", "********",
150+
"--diode-app-name-prefix", d.diodeAppNamePrefix,
151+
}
120152
}
121153

122154
if d.diodeOtelEndpoint != "" {
@@ -125,7 +157,9 @@ func (d *networkDiscoveryBackend) Start(ctx context.Context, cancelFunc context.
125157

126158
d.logger.Info("network-discovery startup", slog.Any("arguments", pvOptions))
127159

128-
pvOptions[9] = d.diodeClientSecret
160+
if !d.diodeDryRun && len(pvOptions) > 9 {
161+
pvOptions[9] = d.diodeClientSecret
162+
}
129163

130164
d.proc = backend.NewCmdOptions(backend.CmdOptions{
131165
Buffered: false,

agent/backend/networkdiscovery/network_discovery_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,85 @@ func TestNetworkDiscoveryBackendCompleted(t *testing.T) {
201201

202202
assert.Error(t, err)
203203
}
204+
205+
func TestNetworkDiscoveryBackendDryRun(t *testing.T) {
206+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
207+
w.Header().Set("Content-Type", "application/json")
208+
switch {
209+
case r.URL.Path == "/api/v1/status":
210+
response := StatusResponse{Version: "1.3.5", StartTime: "2023-10-01T12:00:00Z", UpTime: 123.456}
211+
_ = json.NewEncoder(w).Encode(response)
212+
case r.URL.Path == "/api/v1/capabilities":
213+
capabilities := map[string]any{"capability": true}
214+
_ = json.NewEncoder(w).Encode(capabilities)
215+
case strings.HasPrefix(r.URL.Path, "/api/v1/policies"):
216+
_ = json.NewEncoder(w).Encode(map[string]any{"status": "ok"})
217+
default:
218+
w.WriteHeader(http.StatusNotFound)
219+
}
220+
}))
221+
defer server.Close()
222+
223+
serverURL, err := url.Parse(server.URL)
224+
assert.NoError(t, err)
225+
226+
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
227+
repo, err := policies.NewMemRepo()
228+
assert.NoError(t, err)
229+
230+
mockCmd := &mocks.MockCmd{}
231+
mocks.SetupSuccessfulProcess(mockCmd, 12345)
232+
233+
originalNewCmdOptions := backend.NewCmdOptions
234+
defer func() {
235+
backend.NewCmdOptions = originalNewCmdOptions
236+
}()
237+
238+
backend.NewCmdOptions = func(options backend.CmdOptions, name string, args ...string) backend.Commander {
239+
assert.Equal(t, "network-discovery", name, "Expected command name to be network-discovery")
240+
assert.Contains(t, args, "--dry-run")
241+
assert.Contains(t, args, "--dry-run-output-dir")
242+
assert.NotContains(t, args, "--host")
243+
assert.NotContains(t, args, "--port")
244+
assert.False(t, options.Buffered, "Expected buffered to be false")
245+
assert.True(t, options.Streaming, "Expected streaming to be true")
246+
return mockCmd
247+
}
248+
249+
assert.True(t, networkdiscovery.Register())
250+
be := backend.GetBackend("network_discovery")
251+
252+
beCommons := config.BackendCommons{
253+
Diode: struct {
254+
Target string `yaml:"target"`
255+
ClientID string `yaml:"client_id"`
256+
ClientSecret string `yaml:"client_secret"`
257+
AgentName string `yaml:"agent_name"`
258+
DryRun bool `yaml:"dry_run"`
259+
DryRunOutputDir string `yaml:"dry_run_output_dir"`
260+
}{
261+
DryRun: true,
262+
DryRunOutputDir: "/tmp/dry-run-output",
263+
},
264+
}
265+
266+
err = be.Configure(logger, repo, map[string]any{
267+
"host": serverURL.Hostname(),
268+
"port": serverURL.Port(),
269+
}, beCommons)
270+
assert.NoError(t, err)
271+
272+
ctx, cancel := context.WithCancel(context.Background())
273+
err = be.Start(ctx, cancel)
274+
assert.NoError(t, err)
275+
276+
assert.False(t, be.GetStartTime().IsZero())
277+
278+
err = be.RemovePolicy(policies.PolicyData{ID: "1", Name: "policy", Data: map[string]any{"k": "v"}})
279+
assert.NoError(t, err)
280+
281+
err = be.Stop(context.WithValue(context.Background(), config.ContextKey("routine"), "test"))
282+
assert.NoError(t, err)
283+
284+
mockCmd.AssertExpectations(t)
285+
}

0 commit comments

Comments
 (0)