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
133 changes: 133 additions & 0 deletions .github/workflows/pr-semantic-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
name: Orb Agent - PR Semantic Release Check
on:
pull_request:
branches: [ release ]

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true

env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEMANTIC_RELEASE_PACKAGE: ${{ github.repository }}
APP_NAME: orb-agent

permissions:
contents: read
pull-requests: write

jobs:
semantic-release-dry-run:
name: Semantic Release Dry Run
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"

- name: Write package.json
uses: DamianReeves/write-file-action@6929a9a6d1807689191dcc8bbe62b54d70a32b42 #v1.3
with:
path: ./package.json
write-mode: overwrite
contents: |
{
"name": "${{ env.APP_NAME }}",
"version": "1.0.0",
"devDependencies": {
"semantic-release-export-data": "^1.0.1",
"@semantic-release/changelog": "^6.0.3"
}
}

- name: Write .releaserc.json
uses: DamianReeves/write-file-action@6929a9a6d1807689191dcc8bbe62b54d70a32b42 #v1.3
with:
path: ./.releaserc.json
write-mode: overwrite
contents: |
{
"branches": "release",
"repositoryUrl": "https://github.com/netboxlabs/orb-agent",
"debug": "true",
"tagFormat": "v${version}",
"plugins": [
["semantic-release-export-data"],
["@semantic-release/commit-analyzer", {
"releaseRules": [
{ "message": "*", "release": "patch"},
{ "message": "fix*", "release": "patch" },
{ "message": "feat*", "release": "minor" },
{ "message": "major*", "release": "major" }
]
}],
"@semantic-release/release-notes-generator",
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md",
"changelogTitle": "# Semantic Versioning Changelog"
}
],
[
"@semantic-release/github",
{
"assets": [
{
"path": "release/**"
}
]
}
]
]
}

- name: Install dependencies
run: npm i

- name: Run semantic-release dry-run
id: semantic-release
run: npx semantic-release --debug --dry-run

- name: Comment PR with results
uses: actions/github-script@v7
with:
script: |
const { data: commits } = await github.rest.pulls.listCommits({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number
});

const commitMessages = commits.map(commit => commit.commit.message).join('\n');

let comment = `## Semantic Release Dry Run Results 🔍\n\n`;

if (process.env.NEW_RELEASE_PUBLISHED === 'true') {
comment += `✅ **A new release would be published!**\n\n`;
comment += `**Version:** ${process.env.NEW_RELEASE_VERSION}\n\n`;
comment += `**Commit Messages:**\n\`\`\`\n${commitMessages}\n\`\`\`\n\n`;
comment += `This PR would trigger a ${process.env.NEW_RELEASE_TYPE || 'patch'} release.`;
} else {
comment += `ℹ️ **No new release would be published**\n\n`;
comment += `**Commit Messages:**\n\`\`\`\n${commitMessages}\n\`\`\`\n\n`;
comment += `No semantic changes detected that would trigger a release.`;
}

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
env:
NEW_RELEASE_PUBLISHED: ${{ steps.semantic-release.outputs.new-release-published }}
NEW_RELEASE_VERSION: ${{ steps.semantic-release.outputs.new-release-version }}
NEW_RELEASE_TYPE: ${{ steps.semantic-release.outputs.new-release-type }}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ A special `common` subsection under `backends` defines configuration settings th
client_id: ${DIODE_CLIENT_ID}
client_secret: ${DIODE_CLIENT_SECRET}
agent_name: agent01
dry_run: false
dry_run_output_dir: /opt/orb
```

### Policies
Expand Down
60 changes: 47 additions & 13 deletions agent/backend/networkdiscovery/network_discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ type networkDiscoveryBackend struct {
apiPort string
apiProtocol string

diodeTarget string
diodeClientID string
diodeClientSecret string
diodeAppNamePrefix string
diodeOtelEndpoint string
diodeTarget string
diodeClientID string
diodeClientSecret string
diodeAppNamePrefix string
diodeOtelEndpoint string
diodeDryRun bool
diodeDryRunOutputDir string

startTime time.Time
proc backend.Commander
Expand Down Expand Up @@ -84,6 +86,27 @@ func (d *networkDiscoveryBackend) Configure(logger *slog.Logger, repo policies.P
d.diodeClientID = common.Diode.ClientID
d.diodeClientSecret = common.Diode.ClientSecret
d.diodeAppNamePrefix = common.Diode.AgentName
d.diodeDryRun = common.Diode.DryRun
d.diodeDryRunOutputDir = common.Diode.DryRunOutputDir

if target, prs := config["target"].(string); prs {
d.diodeTarget = target
}
if clientID, prs := config["client_id"].(string); prs {
d.diodeClientID = clientID
}
if clientSecret, prs := config["client_secret"].(string); prs {
d.diodeClientSecret = clientSecret
}
if agentName, prs := config["agent_name"].(string); prs {
d.diodeAppNamePrefix = agentName
}
if dryRun, prs := config["dry_run"].(bool); prs {
d.diodeDryRun = dryRun
}
if dryRunOutputDir, prs := config["dry_run_output_dir"].(string); prs {
d.diodeDryRunOutputDir = dryRunOutputDir
}

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

pvOptions := []string{
"--host", d.apiHost,
"--port", d.apiPort,
"--diode-target", d.diodeTarget,
"--diode-client-id", d.diodeClientID,
"--diode-client-secret", "********",
"--diode-app-name-prefix", d.diodeAppNamePrefix,
var pvOptions []string
if d.diodeDryRun {
pvOptions = []string{
"--dry-run",
"--dry-run-output-dir", d.diodeDryRunOutputDir,
"--diode-app-name-prefix", d.diodeAppNamePrefix,
}
} else {
pvOptions = []string{
"--host", d.apiHost,
"--port", d.apiPort,
"--diode-target", d.diodeTarget,
"--diode-client-id", d.diodeClientID,
"--diode-client-secret", "********",
"--diode-app-name-prefix", d.diodeAppNamePrefix,
}
}

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

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

pvOptions[9] = d.diodeClientSecret
if !d.diodeDryRun && len(pvOptions) > 9 {
pvOptions[9] = d.diodeClientSecret
}

d.proc = backend.NewCmdOptions(backend.CmdOptions{
Buffered: false,
Expand Down
82 changes: 82 additions & 0 deletions agent/backend/networkdiscovery/network_discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,85 @@ func TestNetworkDiscoveryBackendCompleted(t *testing.T) {

assert.Error(t, err)
}

func TestNetworkDiscoveryBackendDryRun(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch {
case r.URL.Path == "/api/v1/status":
response := StatusResponse{Version: "1.3.5", StartTime: "2023-10-01T12:00:00Z", UpTime: 123.456}
_ = json.NewEncoder(w).Encode(response)
case r.URL.Path == "/api/v1/capabilities":
capabilities := map[string]any{"capability": true}
_ = json.NewEncoder(w).Encode(capabilities)
case strings.HasPrefix(r.URL.Path, "/api/v1/policies"):
_ = json.NewEncoder(w).Encode(map[string]any{"status": "ok"})
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer server.Close()

serverURL, err := url.Parse(server.URL)
assert.NoError(t, err)

logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
repo, err := policies.NewMemRepo()
assert.NoError(t, err)

mockCmd := &mocks.MockCmd{}
mocks.SetupSuccessfulProcess(mockCmd, 12345)

originalNewCmdOptions := backend.NewCmdOptions
defer func() {
backend.NewCmdOptions = originalNewCmdOptions
}()

backend.NewCmdOptions = func(options backend.CmdOptions, name string, args ...string) backend.Commander {
assert.Equal(t, "network-discovery", name, "Expected command name to be network-discovery")
assert.Contains(t, args, "--dry-run")
assert.Contains(t, args, "--dry-run-output-dir")
assert.NotContains(t, args, "--host")
assert.NotContains(t, args, "--port")
assert.False(t, options.Buffered, "Expected buffered to be false")
assert.True(t, options.Streaming, "Expected streaming to be true")
return mockCmd
}

assert.True(t, networkdiscovery.Register())
be := backend.GetBackend("network_discovery")

beCommons := config.BackendCommons{
Diode: struct {
Target string `yaml:"target"`
ClientID string `yaml:"client_id"`
ClientSecret string `yaml:"client_secret"`
AgentName string `yaml:"agent_name"`
DryRun bool `yaml:"dry_run"`
DryRunOutputDir string `yaml:"dry_run_output_dir"`
}{
DryRun: true,
DryRunOutputDir: "/tmp/dry-run-output",
},
}

err = be.Configure(logger, repo, map[string]any{
"host": serverURL.Hostname(),
"port": serverURL.Port(),
}, beCommons)
assert.NoError(t, err)

ctx, cancel := context.WithCancel(context.Background())
err = be.Start(ctx, cancel)
assert.NoError(t, err)

assert.False(t, be.GetStartTime().IsZero())

err = be.RemovePolicy(policies.PolicyData{ID: "1", Name: "policy", Data: map[string]any{"k": "v"}})
assert.NoError(t, err)

err = be.Stop(context.WithValue(context.Background(), config.ContextKey("routine"), "test"))
assert.NoError(t, err)

mockCmd.AssertExpectations(t)
}
Loading