Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Security
### Added

- **OEM distribution breakdown:** Added `GET /api/apps/{app}/groups/{group}/oem_breakdown` and a group-card OEM/platform chart (alongside version breakdown). Empty OEM values are reported as `unknown`. ([#1569](https://github.com/flatcar/nebraska/issues/1569))
- **Custom CA Certificate for TLS:** Added `--ca-file` flag to trust additional CA certificates for TLS verification (e.g., internal CA, Let's Encrypt staging). Applies to the OIDC provider client and the syncer. Supports multiple PEM-encoded certs, additive to system CAs. Also exposed as `config.caFile` in the Helm chart.
- **OEM Attribute Capture:** Instances now store OEM and Aleph version information from Omaha update requests. ([#1286](https://github.com/flatcar/nebraska/pull/1286))
- **Multi-Step Updates with Floor Packages:** Added support for mandatory intermediate update versions (floor packages) that clients must install before reaching the target version. This enables safe migration paths for breaking changes by ensuring clients update through specific versions in order. Floor packages can be configured per channel with optional reasons and are architecture-specific. ([#1195](https://github.com/flatcar/nebraska/pull/1195))
Expand Down
50 changes: 50 additions & 0 deletions backend/api/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,36 @@ paths:
description: Group not found response
"500":
description: Version Breakdown of group error response
/api/apps/{appIDorProductID}/groups/{groupID}/oem_breakdown:
get:
description: get OEM (platform) breakdown of a group given its groupID and appID
operationId: getGroupOEMBreakdown
security:
- oidcBearerAuth: []
- oidcCookieAuth: []
- githubCookieAuth: []
parameters:
- in: path
name: appIDorProductID
required: true
schema:
type: string
- in: path
name: groupID
required: true
schema:
type: string
responses:
"200":
description: OEM Breakdown of group success response
content:
application/json:
schema:
$ref: "#/components/schemas/groupOEMBreakdown"
"404":
description: Group not found response
"500":
description: OEM Breakdown of group error response
/api/apps/{appIDorProductID}/channels:
get:
description: paginate channels of an app
Expand Down Expand Up @@ -1815,6 +1845,26 @@ components:
items:
$ref: "#/components/schemas/versionBreakdownEntry"

oemBreakdownEntry:
type: object
required:
- oem
- instances
- percentage
properties:
oem:
type: string
instances:
type: integer
percentage:
type: number
format: double

groupOEMBreakdown:
type: array
items:
$ref: "#/components/schemas/oemBreakdownEntry"

application:
type: object
required:
Expand Down
49 changes: 49 additions & 0 deletions backend/pkg/api/groups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,55 @@ func TestVersionBreakDownEmpty(t *testing.T) {
assert.Len(t, versionBreakdown, 0)
}

func TestGetGroupOEMBreakdown(t *testing.T) {
a := newForTest(t)
defer a.Close()
as := adminSvc(a)
rs := runtimeSvc(a)

tTeam, err := as.AddTeam(&types.Team{Name: "oem_bd_team"})
assert.NoError(t, err)
tApp, err := as.AddApp(&types.Application{Name: "oem_bd_app", TeamID: tTeam.ID})
assert.NoError(t, err)
tPkg, err := as.AddPackage(&types.Package{Type: types.PkgTypeOther, URL: "http://sample.url/pkg", Version: "12.1.0", ApplicationID: tApp.ID})
assert.NoError(t, err)
tChannel, err := as.AddChannel(&types.Channel{Name: "oem_bd_channel", Color: "blue", ApplicationID: tApp.ID, PackageID: null.StringFrom(tPkg.ID)})
assert.NoError(t, err)
tGroup, err := as.AddGroup(&types.Group{Name: "oem_bd_group", ApplicationID: tApp.ID, ChannelID: null.StringFrom(tChannel.ID), PolicyUpdatesEnabled: true, PolicySafeMode: true, PolicyPeriodInterval: "15 minutes", PolicyMaxUpdatesPerPeriod: 2, PolicyUpdateTimeout: "60 minutes"})
assert.NoError(t, err)

_, err = rs.RegisterInstance(types.Instance{ID: uuid.New().String(), IP: "10.0.0.1", OEM: "azure"}, runtime.NewInstanceApplication(tApp.ID, tGroup.ID, "1.0.0"))
assert.NoError(t, err)
_, err = rs.RegisterInstance(types.Instance{ID: uuid.New().String(), IP: "10.0.0.2", OEM: "azure"}, runtime.NewInstanceApplication(tApp.ID, tGroup.ID, "1.0.0"))
assert.NoError(t, err)
_, err = rs.RegisterInstance(types.Instance{ID: uuid.New().String(), IP: "10.0.0.3", OEM: "aws"}, runtime.NewInstanceApplication(tApp.ID, tGroup.ID, "1.0.0"))
assert.NoError(t, err)
_, err = rs.RegisterInstance(types.Instance{ID: uuid.New().String(), IP: "10.0.0.4"}, runtime.NewInstanceApplication(tApp.ID, tGroup.ID, "1.0.0"))
assert.NoError(t, err)

breakdown, err := a.GetGroupOEMBreakdown(tGroup.ID)
assert.NoError(t, err)
if assert.Len(t, breakdown, 3) {
assert.Equal(t, "azure", breakdown[0].OEM)
assert.Equal(t, 2, breakdown[0].Instances)
assert.InDelta(t, 50.0, breakdown[0].Percentage, 0.01)

assert.Equal(t, "aws", breakdown[1].OEM)
assert.Equal(t, 1, breakdown[1].Instances)
assert.InDelta(t, 25.0, breakdown[1].Percentage, 0.01)

assert.Equal(t, "unknown", breakdown[2].OEM)
assert.Equal(t, 1, breakdown[2].Instances)
assert.InDelta(t, 25.0, breakdown[2].Percentage, 0.01)
}

total := 0.0
for _, e := range breakdown {
total += e.Percentage
}
assert.InDelta(t, 100.0, total, 0.01)
}

func TestGroupTrackName(t *testing.T) {
a := newForTest(t)
defer a.Close()
Expand Down
36 changes: 36 additions & 0 deletions backend/pkg/api/internal/dbreads/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,42 @@ func (q *Queries) GetGroupVersionBreakdown(groupID string) ([]*types.VersionBrea
return entryList, nil
}

// GetGroupOEMBreakdown returns an OEM (platform) breakdown of all instances
// running on a given group. Empty OEM values are reported as "unknown".
func (q *Queries) GetGroupOEMBreakdown(groupID string) ([]*types.OEMBreakdownEntry, error) {
entryList := []*types.OEMBreakdownEntry{}

query := fmt.Sprintf(`
SELECT COALESCE(NULLIF(i.oem, ''), 'unknown') AS oem,
count(*) AS instances,
(count(*) * 100.0 / sum(count(*)) OVER ()) AS percentage
FROM instance_application ia
JOIN instance i ON i.id = ia.instance_id
WHERE ia.group_id = $1
AND ia.last_check_for_updates > now() - interval '%[1]s'
AND %[2]s
GROUP BY oem
ORDER BY instances DESC, oem ASC
`, validityInterval, ignoreFakeInstanceCondition("ia.instance_id"))
rows, err := q.db.Queryx(query, groupID)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var entry types.OEMBreakdownEntry
err := rows.StructScan(&entry)
if err != nil {
return nil, err
}
entryList = append(entryList, &entry)
}
if err := rows.Err(); err != nil {
return nil, err
}
return entryList, nil
}

// getGroupInstancesStats returns a summary of the status of the
// instances that belong to a given group.
func (q *Queries) GetGroupInstancesStats(groupID, duration string) (*types.InstancesStatusStats, error) {
Expand Down
8 changes: 8 additions & 0 deletions backend/pkg/api/types/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ type VersionBreakdownEntry struct {
Percentage float64 `db:"percentage" json:"percentage"`
}

// OEMBreakdownEntry represents the distribution of OEMs (cloud/hardware
// platforms) among instances belonging to a given group.
type OEMBreakdownEntry struct {
OEM string `db:"oem" json:"oem"`
Instances int `db:"instances" json:"instances"`
Percentage float64 `db:"percentage" json:"percentage"`
}

type VersionCountTimelineEntry struct {
Time time.Time `db:"ts" json:"time"`
Version string `db:"version" json:"version"`
Expand Down
116 changes: 116 additions & 0 deletions backend/pkg/codegen/client.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading