Skip to content

Commit

Permalink
Merge branch 'master' into refac/users
Browse files Browse the repository at this point in the history
  • Loading branch information
leohhhn authored Dec 18, 2024
2 parents 75a87e0 + a7fdd70 commit e92964c
Show file tree
Hide file tree
Showing 84 changed files with 5,207 additions and 520 deletions.
48 changes: 48 additions & 0 deletions .github/workflows/bot-proxy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# This workflow must be kept in sync to some extent with bot.yml
name: GitHub Bot Proxy

on:
# Watch for any completed run on bot.yml workflow
workflow_run:
workflows: [GitHub Bot]
types: [completed]

jobs:
# This workflow monitors any run completed on the GitHub Bot workflow and
# checks if the event that triggered it is limited to read-only permissions
# (e.g 'pull_request_review' on a pull request opened from a fork).
# In this case, it reruns the GitHub Bot workflow using a 'workflow_dispatch'
# event, thereby allowing it to run with write permissions.
#
# Complete flow:
# 'pull_request_review' from fork on bot.yml (read-only) -> 'workflow_run' on bot-proxy.yml (write) -> 'workflow_dispatch' on bot.yml (write)
rerun-with-write-perm:
name: Rerun Bot with write permission
# Skip this workflow if the original event is not 'pull_request_review'
if: github.event.workflow_run.event == 'pull_request_review'
runs-on: ubuntu-latest
permissions:
actions: write

steps:
- name: Download artifact from previous run
uses: actions/download-artifact@v4
with:
name: pr-number
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
# Even if the artifact doesn't exist, do not mark the workflow as failed
# Useful if the 'pull_request_review' event was emitted by a PR opened
# from a branch on the main repo, so it has already been processed by
# the bot workflow, and no artifact has been uploaded.
continue-on-error: true
id: download

- name: Send workflow_dispatch event to Github Bot
# Run only if an artifact was downloaded
if: steps.download.outcome == 'success'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.event.workflow_run.repository.full_name }}
run: |
gh workflow run bot.yml -R "$REPO" -f "pull-request-list=$(cat pr-number)"
36 changes: 33 additions & 3 deletions .github/workflows/bot.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# This workflow must be kept in sync to some extent with bot-proxy.yml
name: GitHub Bot

on:
Expand Down Expand Up @@ -37,8 +38,14 @@ jobs:
# handle the parallel processing of the pull-requests
define-prs-matrix:
name: Define PRs matrix
# Prevent bot from retriggering itself and ignore event emitted by codecov
if: ${{ github.actor != vars.GH_BOT_LOGIN && github.actor != 'codecov[bot]' }}
# Skip this workflow if:
# - the bot is retriggering itself
# - the event is emitted by codecov
# - the event is a review on a pull request from a fork (see save-pr-number job below)
if: |
github.actor != vars.GH_BOT_LOGIN &&
github.actor != 'codecov[bot]' &&
(github.event_name != 'pull_request_review' || github.event.pull_request.base.repo.full_name == github.event.pull_request.head.repo.full_name)
runs-on: ubuntu-latest
permissions:
pull-requests: read
Expand All @@ -61,13 +68,36 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: go run . matrix -matrix-key 'pr-numbers' -verbose

# This job is executed if an event with read-only permission has triggered this
# workflow (e.g 'pull_request_review' on a pull request opened from a fork).
# In this case, this job persists the PR number in an artifact so that the
# proxy workflow can use it to rerun the current workflow with write permission.
# See bot-proxy.yml for more info.
save-pr-number:
name: Persist PR number for proxy
# Run this job if the event is a review on a pull request opened from a fork
if: github.event_name == 'pull_request_review' && github.event.pull_request.base.repo.full_name != github.event.pull_request.head.repo.full_name
runs-on: ubuntu-latest

steps:
- name: Write PR number to a file
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: echo $PR_NUMBER > pr-number

- name: Upload it as an artifact
uses: actions/upload-artifact@v4
with:
name: pr-number
path: pr-number

# This job processes each pull request in the matrix individually while ensuring
# that a same PR cannot be processed concurrently by mutliple runners
process-pr:
name: Process PR
needs: define-prs-matrix
# Just skip this job if PR numbers matrix is empty (prevent failed state)
if: ${{ needs.define-prs-matrix.outputs.pr-numbers != '[]' && needs.define-prs-matrix.outputs.pr-numbers != '' }}
if: needs.define-prs-matrix.outputs.pr-numbers != '[]' && needs.define-prs-matrix.outputs.pr-numbers != ''
runs-on: ubuntu-latest
strategy:
matrix:
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,19 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: echo "GOROOT=$(go env GOROOT)" >> $GITHUB_ENV
- run: echo $GOROOT
- run: "cd misc/stdlib_diff && make gen"
- run: "cd misc/gendocs && make install gen"
- run: "mkdir -p pages_ouput/stdlib_diff"
- run: |
mv misc/gendocs/godoc pages_output
mv misc/stdlib_diff/stdlib_diff pages_ouput/stdlib_diff
- uses: actions/configure-pages@v5
id: pages
- uses: actions/upload-pages-artifact@v3
with:
path: ./misc/gendocs/godoc
path: ./pages_output

deploy:
if: ${{ github.repository == 'gnolang/gno' }} # Alternatively, validate based on provided tokens and permissions.
Expand Down
7 changes: 1 addition & 6 deletions contribs/github-bot/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type ManualCheck struct {

// This is the description for a persistent rule with a non-standard behavior
// that allow maintainer to force the "success" state of the CI check
const ForceSkipDescription = "**SKIP**: Do not block the CI for this PR"
const ForceSkipDescription = "**IGNORE** the bot requirements for this PR (force green CI check)"

// This function returns the configuration of the bot consisting of automatic and manual checks
// in which the GitHub client is injected.
Expand All @@ -35,11 +35,6 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) {
If: c.CreatedFromFork(),
Then: r.MaintainerCanModify(),
},
{
Description: "The pull request head branch must be up-to-date with its base ([more info](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/keeping-your-pull-request-in-sync-with-the-base-branch))",
If: c.Always(),
Then: r.UpToDateWith(gh, r.PR_BASE),
},
{
Description: "Changes to 'docs' folder must be reviewed/authored by at least one devrel and one tech-staff",
If: c.FileChanged(gh, "^docs/"),
Expand Down
4 changes: 2 additions & 2 deletions contribs/gnodev/cmd/gnobro/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,14 +429,14 @@ func getSignerForAccount(io commands.IO, address string, kb keys.Keybase, cfg *b
}

// try empty password first
if _, err := kb.ExportPrivKeyUnsafe(address, ""); err != nil {
if _, err := kb.ExportPrivKey(address, ""); err != nil {
prompt := fmt.Sprintf("[%.10s] Enter password:", address)
signer.Password, err = io.GetPassword(prompt, true)
if err != nil {
return nil, fmt.Errorf("error while reading password: %w", err)
}

if _, err := kb.ExportPrivKeyUnsafe(address, signer.Password); err != nil {
if _, err := kb.ExportPrivKey(address, signer.Password); err != nil {
return nil, fmt.Errorf("invalid password: %w", err)
}
}
Expand Down
4 changes: 2 additions & 2 deletions examples/gno.land/p/demo/avl/pager/pager.gno
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// Pager is a struct that holds the AVL tree and pagination parameters.
type Pager struct {
Tree *avl.Tree
Tree avl.ITree
PageQueryParam string
SizeQueryParam string
DefaultPageSize int
Expand All @@ -37,7 +37,7 @@ type Item struct {
}

// NewPager creates a new Pager with default values.
func NewPager(tree *avl.Tree, defaultPageSize int, reversed bool) *Pager {
func NewPager(tree avl.ITree, defaultPageSize int, reversed bool) *Pager {
return &Pager{
Tree: tree,
PageQueryParam: "page",
Expand Down
1 change: 1 addition & 0 deletions examples/gno.land/p/demo/avl/rotree/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/demo/avl/rotree
162 changes: 162 additions & 0 deletions examples/gno.land/p/demo/avl/rotree/rotree.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Package rotree provides a read-only wrapper for avl.Tree with safe value transformation.
//
// It is useful when you want to expose a read-only view of a tree while ensuring that
// the sensitive data cannot be modified.
//
// Example:
//
// // Define a user structure with sensitive data
// type User struct {
// Name string
// Balance int
// Internal string // sensitive field
// }
//
// // Create and populate the original tree
// privateTree := avl.NewTree()
// privateTree.Set("alice", &User{
// Name: "Alice",
// Balance: 100,
// Internal: "sensitive",
// })
//
// // Create a safe transformation function that copies the struct
// // while excluding sensitive data
// makeEntrySafeFn := func(v interface{}) interface{} {
// u := v.(*User)
// return &User{
// Name: u.Name,
// Balance: u.Balance,
// Internal: "", // omit sensitive data
// }
// }
//
// // Create a read-only view of the tree
// PublicTree := rotree.Wrap(tree, makeEntrySafeFn)
//
// // Safely access the data
// value, _ := roTree.Get("alice")
// user := value.(*User)
// // user.Name == "Alice"
// // user.Balance == 100
// // user.Internal == "" (sensitive data is filtered)
package rotree

import (
"gno.land/p/demo/avl"
)

// Wrap creates a new ReadOnlyTree from an existing avl.Tree and a safety transformation function.
// If makeEntrySafeFn is nil, values will be returned as-is without transformation.
//
// makeEntrySafeFn is a function that transforms a tree entry into a safe version that can be exposed to external users.
// This function should be implemented based on the specific safety requirements of your use case:
//
// 1. No-op transformation: For primitive types (int, string, etc.) or already safe objects,
// simply pass nil as the makeEntrySafeFn to return values as-is.
//
// 2. Defensive copying: For mutable types like slices or maps, you should create a deep copy
// to prevent modification of the original data.
// Example: func(v interface{}) interface{} { return append([]int{}, v.([]int)...) }
//
// 3. Read-only wrapper: Return a read-only version of the object that implements
// a limited interface.
// Example: func(v interface{}) interface{} { return NewReadOnlyObject(v) }
//
// 4. DAO transformation: Transform the object into a data access object that
// controls how the underlying data can be accessed.
// Example: func(v interface{}) interface{} { return NewDAO(v) }
//
// The function ensures that the returned object is safe to expose to untrusted code,
// preventing unauthorized modifications to the original data structure.
func Wrap(tree *avl.Tree, makeEntrySafeFn func(interface{}) interface{}) *ReadOnlyTree {
return &ReadOnlyTree{
tree: tree,
makeEntrySafeFn: makeEntrySafeFn,
}
}

// ReadOnlyTree wraps an avl.Tree and provides read-only access.
type ReadOnlyTree struct {
tree *avl.Tree
makeEntrySafeFn func(interface{}) interface{}
}

// Verify that ReadOnlyTree implements ITree
var _ avl.ITree = (*ReadOnlyTree)(nil)

// getSafeValue applies the makeEntrySafeFn if it exists, otherwise returns the original value
func (roTree *ReadOnlyTree) getSafeValue(value interface{}) interface{} {
if roTree.makeEntrySafeFn == nil {
return value
}
return roTree.makeEntrySafeFn(value)
}

// Size returns the number of key-value pairs in the tree.
func (roTree *ReadOnlyTree) Size() int {
return roTree.tree.Size()
}

// Has checks whether a key exists in the tree.
func (roTree *ReadOnlyTree) Has(key string) bool {
return roTree.tree.Has(key)
}

// Get retrieves the value associated with the given key, converted to a safe format.
func (roTree *ReadOnlyTree) Get(key string) (interface{}, bool) {
value, exists := roTree.tree.Get(key)
if !exists {
return nil, false
}
return roTree.getSafeValue(value), true
}

// GetByIndex retrieves the key-value pair at the specified index in the tree, with the value converted to a safe format.
func (roTree *ReadOnlyTree) GetByIndex(index int) (string, interface{}) {
key, value := roTree.tree.GetByIndex(index)
return key, roTree.getSafeValue(value)
}

// Iterate performs an in-order traversal of the tree within the specified key range.
func (roTree *ReadOnlyTree) Iterate(start, end string, cb avl.IterCbFn) bool {
return roTree.tree.Iterate(start, end, func(key string, value interface{}) bool {
return cb(key, roTree.getSafeValue(value))
})
}

// ReverseIterate performs a reverse in-order traversal of the tree within the specified key range.
func (roTree *ReadOnlyTree) ReverseIterate(start, end string, cb avl.IterCbFn) bool {
return roTree.tree.ReverseIterate(start, end, func(key string, value interface{}) bool {
return cb(key, roTree.getSafeValue(value))
})
}

// IterateByOffset performs an in-order traversal of the tree starting from the specified offset.
func (roTree *ReadOnlyTree) IterateByOffset(offset int, count int, cb avl.IterCbFn) bool {
return roTree.tree.IterateByOffset(offset, count, func(key string, value interface{}) bool {
return cb(key, roTree.getSafeValue(value))
})
}

// ReverseIterateByOffset performs a reverse in-order traversal of the tree starting from the specified offset.
func (roTree *ReadOnlyTree) ReverseIterateByOffset(offset int, count int, cb avl.IterCbFn) bool {
return roTree.tree.ReverseIterateByOffset(offset, count, func(key string, value interface{}) bool {
return cb(key, roTree.getSafeValue(value))
})
}

// Set is not supported on ReadOnlyTree and will panic.
func (roTree *ReadOnlyTree) Set(key string, value interface{}) bool {
panic("Set operation not supported on ReadOnlyTree")
}

// Remove is not supported on ReadOnlyTree and will panic.
func (roTree *ReadOnlyTree) Remove(key string) (value interface{}, removed bool) {
panic("Remove operation not supported on ReadOnlyTree")
}

// RemoveByIndex is not supported on ReadOnlyTree and will panic.
func (roTree *ReadOnlyTree) RemoveByIndex(index int) (key string, value interface{}) {
panic("RemoveByIndex operation not supported on ReadOnlyTree")
}
Loading

0 comments on commit e92964c

Please sign in to comment.