From 54e418acaf960c5ed8be7f4a29616ae7b5eb75f797f7ed4d487f79485d056108 Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Sun, 21 Jul 2024 22:15:11 +0200 Subject: [PATCH] . --- bots-common/obs.api.yaml | 103 +++++++------ bots-common/obs_utils.go | 183 +++++++++++++++++++++++ bots-common/obs_utils_test.go | 266 ++++++++++++++++++++++++++++++++++ obs-staging-bot/main.go | 15 +- 4 files changed, 513 insertions(+), 54 deletions(-) create mode 100644 bots-common/obs_utils.go create mode 100644 bots-common/obs_utils_test.go diff --git a/bots-common/obs.api.yaml b/bots-common/obs.api.yaml index 5ceaa33..a5578e7 100644 --- a/bots-common/obs.api.yaml +++ b/bots-common/obs.api.yaml @@ -11926,13 +11926,13 @@ paths: title: test 1 who: Administrator state: new - when: 2021-09-30 09:12:02.000000000 +02:00 + when: 2021-09-30 07:12:02.000000000 +00:00 event_type: review_wanted - id: 25 title: test 2 who: User 2 event_type: comment_for_package - when: 2021-09-27 09:16:19.000000000 +02:00 + when: 2021-09-27 07:16:19.000000000 +00:00 '401': description: | Unauthorized. @@ -13329,7 +13329,7 @@ paths: description: 'Token description. It helps to identify a token from the rest of the tokens of a user. - ' +' example: Rebuild gchz - in: query name: project @@ -17210,7 +17210,7 @@ paths: description: 'Diff relative to a given superseded request. State the id of the corresponding superseded request. - ' +' example: 10401 - in: query name: view @@ -18522,7 +18522,7 @@ paths: owner: login: user_1 email: user_1@example.com - realname: + realname: - name: 4883 created_at: 2018-12-11 03:22:54 UTC updated_at: 2018-12-11 03:22:54 UTC @@ -18693,7 +18693,7 @@ paths: code: invalid_attribute summary: 'Attribute ''foo'' must be in the $NAMESPACE:$NAME style - ' +' '401': description: | Unauthorized. @@ -18799,21 +18799,21 @@ paths: type: string description: 'The name of a binary package whose owners you want to search. - ' +' - in: query name: user schema: type: string description: 'Search what this user is owner of. - ' +' - in: query name: group schema: type: string description: 'The title of a group. Search what this group is owner of. - ' +' - in: query name: limit schema: @@ -19014,7 +19014,7 @@ paths: summary: 'The search needs at least a ''binary'', ''package'' or ''user'' parameter - ' +' search root not set: summary: Search root is not set description: At least one of the projects in your instance should @@ -19023,7 +19023,7 @@ paths: summary: 'The attribute OBS:OwnerRootProject is not set to define default projects. - ' +' '401': description: | Unauthorized. @@ -19768,14 +19768,14 @@ paths: matches: 2 project: - name: home:Iggy - title: - description: + title: + description: person: userid: Iggy role: maintainer - name: home:Iggy:branches:home:Admin title: East of Eden - description: + description: '400': description: Bad Request content: @@ -20454,7 +20454,7 @@ paths: description: 'Empty result, when there is no matches. For example: `?match=project=''home:bs-team:OBS''`. - ' +' '400': description: Bad Request content: @@ -20624,7 +20624,7 @@ paths: description: 'Empty result, when there is no matches. For example: `?match=project=''home:bs-team:OBS''`. - ' +' '400': description: Bad Request content: @@ -21708,7 +21708,7 @@ paths: when: '2022-08-22T09:37:09' who: Requestor by_group: group_1 - comment: + comment: history: who: Admin when: '2022-08-22T09:37:09' @@ -21744,7 +21744,7 @@ paths: when: '2022-08-22T09:37:09' who: Admin by_group: group_1 - comment: + comment: history: who: Admin when: '2022-08-22T09:37:09' @@ -21785,7 +21785,7 @@ paths: when: '2022-08-22T09:37:09' who: Admin by_group: group_1 - comment: + comment: history: who: Admin when: '2022-08-22T09:37:09' @@ -24848,8 +24848,8 @@ paths: srcmd5: 6c73fc9ef8d43369f0778564617b4a93 time: 1682497725 user: Admin - comment: - requestid: + comment: + requestid: '400': description: 'Error: Bad request.' content: @@ -24882,7 +24882,7 @@ paths: summary: 'pattern validation error: 1:1: FATAL: Start tag expected, < not found - ' +' '401': description: | Unauthorized. @@ -30026,7 +30026,7 @@ paths: description: 'Passing the number of the revision, this endpoint displays the attributes'' XML as it was at that point. - ' +' example: 3 - name: view in: query @@ -30052,7 +30052,7 @@ paths: description: 'Passing `with_project`, the response displays the attributes of the package''s project in addition to the package ones. - ' +' example: 1 responses: '200': @@ -30438,7 +30438,7 @@ paths: description: 'Passing the number of the revision, this endpoint displays the attributes'' XML as it was at that point. - ' +' example: 3 - name: view in: query @@ -30465,7 +30465,7 @@ paths: attribute of the package''s project, if any, in addition to the package one. - ' +' example: 1 responses: '200': @@ -31231,6 +31231,13 @@ paths: required: true description: Package name example: ctris + - in: query + name: deleted + schema: + type: string + enum: + - + description: Set to retrieve the package metadata of a deleted package. - in: query name: meta schema: @@ -31920,7 +31927,7 @@ paths: type: string description: 'Set to 1 to allow overwriting of a pre-existing package. - ' +' example: 1 - in: query name: rev @@ -32247,7 +32254,7 @@ paths: time: 1678785078 user: Admin comment: Copying the build environment from origin - requestid: + requestid: '400': description: | Bad Request. @@ -32493,8 +32500,8 @@ paths: version: 2.10~pre time: 1665060711 user: Admin - comment: - requestid: + comment: + requestid: '400': description: Bad Request. content: @@ -32751,8 +32758,8 @@ paths: version: 2.10~pre time: 1665060711 user: Admin - comment: - requestid: + comment: + requestid: '400': description: Bad Request. content: @@ -33389,7 +33396,7 @@ paths: - 0 description: Set to `1` to only show changed files, and not details inside files. - example: + example: - in: query name: olinkrev schema: @@ -33733,7 +33740,7 @@ paths: type: string description: 'Set to 1 to allow overwriting of a pre-existing package. - ' +' example: 1 - name: add_repositories_rebuild in: query @@ -33879,7 +33886,7 @@ paths: - getprojectservices responses: '200': - description: + description: content: application/xml; charset=utf-8: schema: @@ -34475,7 +34482,7 @@ paths: description: 'Link revision in base package. Set to `base` to use the commit revision. - ' +' example: base - in: query name: rev @@ -34752,8 +34759,8 @@ paths: version: 2.10~pre time: 1665060711 user: Admin - comment: - requestid: + comment: + requestid: '400': description: | Bad Request. @@ -34975,7 +34982,7 @@ paths: description: 'Limit the release to a certain repository, set on the project repository definitions. - ' +' example: openSUSE_Tumbleweed - in: query name: setrelease @@ -34984,7 +34991,7 @@ paths: description: 'If this parameter is present, the release will be tagged with this parameter''s value. - ' +' example: Build8.18 - in: query name: arch @@ -37888,8 +37895,8 @@ paths: version: 1 time: 1678787266 user: Admin - comment: - requestid: + comment: + requestid: '401': description: | Unauthorized. @@ -39460,7 +39467,7 @@ paths: obsolete requests as well as missing reviews, otherwise don''t pass this query parameter." - ' +' - name: status in: query schema: @@ -39470,7 +39477,7 @@ paths: status xml (broken packages, missing reviews, checks, etc.), otherwise don''t pass this query parameter" - ' +' - name: history in: query schema: @@ -39859,7 +39866,7 @@ paths: obsolete requests as well as missing reviews, otherwise don''t pass this query parameter. - ' +' - name: status in: query schema: @@ -39870,7 +39877,7 @@ paths: status xml (broken packages, missing reviews, checks, etc.), otherwise don''t pass this query parameter. - ' +' - name: history in: query schema: @@ -40545,7 +40552,7 @@ paths: summary: 'Request ID currently excluded from project project_name. Use --remove-exclusion if you want to force this action. - ' +' Unknown Request: value: code: invalid_request @@ -47425,7 +47432,7 @@ paths: - name: x86_64:1a1f67b948b6:2 no_workers: summary: No Workers Satisfy the Constraints - value: + value: '400': description: Bad Request. content: diff --git a/bots-common/obs_utils.go b/bots-common/obs_utils.go new file mode 100644 index 0000000..67d6d31 --- /dev/null +++ b/bots-common/obs_utils.go @@ -0,0 +1,183 @@ +package common + +import ( + "encoding/xml" + "fmt" + "io" + "log" + "net/http" + "net/url" +) + +type ObsClient struct { + baseUrl *url.URL + client *http.Client + user, password string + cookie string +} + +func NewObsClient(host, username, password string) (*ObsClient, error) { + baseUrl, err := url.Parse("https://" + host) + if err != nil { + return nil, err + } + + return &ObsClient{ + baseUrl: baseUrl, + client: &http.Client{}, + user: username, + password: password, + }, nil +} + +type RepositoryMeta struct { + Name string `xml:"name,attr"` + Arch []string `xml:"arch"` + Path []struct { + XMLName xml.Name `xml:"path"` + Project string `xml:"project,attr"` + Repository string `xml:"repository,attr"` + } +} + +type ProjectMeta struct { + XMLName xml.Name `xml:"project"` + Name string `xml:"name,attr"` + Title string `xml:"title"` + Description string `xml:"description"` + ScmSync string `xml:"xmlsync"` + Repositories []Repository `xml:"repository"` +} + +func parseProjectMeta(data []byte) (*ProjectMeta, error) { + var meta ProjectMeta + err := xml.Unmarshal(data, &meta) + if err != nil { + return nil, err + } + + return &meta, nil +} + +func (c *ObsClient) GetProjectMeta(project string) (*ProjectMeta, error) { + + req, err := http.NewRequest("GET", c.baseUrl.JoinPath("source", project, "_meta").String(), nil) + if err != nil { + return nil, err + } + req.SetBasicAuth(c.user, c.password) + res, err := c.client.Do(req) + + if err != nil { + return nil, err + } + + switch res.StatusCode { + case 200: + break + case 404: + return nil, nil + default: + return nil, fmt.Errorf("Unexpected return code: %d", res.StatusCode) + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + return parseProjectMeta(data) +} + +func (c *ObsClient) DeleteProject(project string) error { + req, err := http.NewRequest("DELETE", c.baseUrl.JoinPath("source", project).String(), nil) + if err != nil { + return err + } + + req.SetBasicAuth(c.user, c.password) + res, err := c.client.Do(req) + + if err != nil { + return err + } + + if res.StatusCode != 200 { + return fmt.Errorf("Unexpected return code: %d", res.StatusCode) + } + + return nil + +} + +type BuildStatus struct { + Package string `xml:"package,attr"` + Code string `xml:"code,attr"` + Details string `xml:"details"` +} + +type BuildResult struct { + Project string `xml:"project,attr"` + Repository string `xml:"repository,attr"` + Arch string `xml:"arch,attr"` + Code string `xml:"code,attr"` + Status []BuildStatus `xml:"status"` + Binaries []BinaryList `xml:"binarylist"` +} + +type Binary struct { + Size uint64 `xml:"size,attr"` + Filename string `xml:"filename,attr"` + Mtime uint64 `xml:"mtime,attr"` +} + +type BinaryList struct { + Package string `xml:"package,attr"` + Binary []Binary `xml:"binary"` +} + +type BuildResultList struct { + XMLName xml.Name `xml:"resultlist"` + Result []BuildResult `xml:"result"` +} + +func parseBuildResults(data []byte) (*BuildResultList, error) { + result := BuildResultList{} + err := xml.Unmarshal(data, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func (c *ObsClient) BuildStatus(project string) (*BuildResultList, error) { + u := c.baseUrl.JoinPath("build", project, "_result") + query := u.Query() + query.Add("view", "status") + query.Add("view", "binarylist") + query.Add("multibuild", "1") + u.RawQuery = query.Encode() + req, err := http.NewRequest("GET", u.String(), nil) + log.Print(u.String()) + if err != nil { + return nil, err + } + + req.SetBasicAuth(c.user, c.password) + res, err := c.client.Do(req) + + if err != nil { + return nil, err + } + + if res.StatusCode != 200 { + return nil, fmt.Errorf("Unexpected return code: %d", res.StatusCode) + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + return parseBuildResults(data) +} diff --git a/bots-common/obs_utils_test.go b/bots-common/obs_utils_test.go new file mode 100644 index 0000000..b547281 --- /dev/null +++ b/bots-common/obs_utils_test.go @@ -0,0 +1,266 @@ +package common + +import ( + "testing" +) + +func TestParseProjectMeta(t *testing.T) { + res, err := parseProjectMeta([]byte(metaPrjData)) + + if err != nil { + return t.Fatal(err) + } + + if res. +} + +func TestParsingOfBuildResults(t *testing.T) { + res, err := parseBuildResults([]byte(buildResultData)) + + if err != nil { + t.Fatal(err) + } + + if res.Result[0].Project != "home:adamm" || + res.Result[0].Status[1].Details != "nothing provides libBox2D-devel" || + res.Result[0].Status[0].Code != "excluded" { + + t.Fatal(res.Result) + } + + if res.Result[0].Binaries[0].Package != "Nudoku" || + len(res.Result[0].Binaries[0].Binary) != 0 { + + t.Fatal(res.Result[0].Binaries[0]) + } +} + +const metaPrjData = ` + + Adam's Home Projects + + + + + + + + + + + x86_64 + aarch64 + + + + + aarch64 + + + + + aarch64 + + +` + +const buildResultData = ` + + + + +
nothing provides libBox2D-devel
+
+ +
nothing provides libBox2D-devel
+
+ + + +
conflict in file _service
+
+ + + +
no source uploaded
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
nothing provides libBox2D-devel
+
+ +
nothing provides libBox2D-devel
+
+ + + +
conflict in file _service
+
+ + + +
no source uploaded
+
+ + + + + + + + + + + +
+ + + + + + + +
conflict in file _service
+
+ + + +
no source uploaded
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
conflict in file _service
+
+ + +
have choice for /usr/bin/openssl needed by opendkim: libressl openssl-1_0_0 openssl-3
+
+ +
no source uploaded
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+` diff --git a/obs-staging-bot/main.go b/obs-staging-bot/main.go index 1a18da0..3945eae 100644 --- a/obs-staging-bot/main.go +++ b/obs-staging-bot/main.go @@ -8,7 +8,6 @@ import ( "src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common/gitea-generated/models" - "src.opensuse.org/autogits/common/obs" ) const ( @@ -65,16 +64,12 @@ func processPullNotification(h *common.RequestHandler, notification *models.Noti switch review.State { case common.ReviewStateUnknown, common.ReviewStateRequestReview: - // start review -- create project in OBS - obsClient, _ := obs.NewClient("api.opensuse.org", obs.WithBaseURL("api.opensuse.org")) -// obsClient.GetSourceProjectNameMeta - case common.ReviewStatePending: // waiting for build results case common.ReviewStateApproved: // done, mark notification as read case common.ReviewStateRequestChanges: - // build failures, mark notification as read + // build failures, mark notification as read } } } @@ -114,6 +109,14 @@ func main() { pollWorkNotifications() + c, _ := common.NewObsClient("api.opensuse.org", "autogits_obs_staging_bot", "?E3N9']$YaC9~uR1") + status, err := c.BuildStatus("home:adamm") + if err != nil { + log.Printf("err: %v\n", err) + } else { + log.Print(*status) + } + stuck := make(chan int) <-stuck }