Repository: kolide/osquery-go Branch: master Commit: a88c0766cd0d Files: 43 Total size: 271.5 KB Directory structure: gitextract_qak33u0z/ ├── .github/ │ └── workflows/ │ └── go.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── client.go ├── client_test.go ├── examples/ │ ├── call/ │ │ └── main.go │ ├── config/ │ │ └── main.go │ ├── distributed/ │ │ └── main.go │ ├── logger/ │ │ └── main.go │ ├── query/ │ │ └── main.go │ └── table/ │ └── main.go ├── gen/ │ └── osquery/ │ ├── GoUnusedProtection__.go │ ├── osquery-consts.go │ └── osquery.go ├── go.mod ├── go.sum ├── locker.go ├── locker_test.go ├── mock/ │ └── osquery.go ├── mock_manager.go ├── osquery.thrift ├── plugin/ │ ├── config/ │ │ ├── config.go │ │ └── config_test.go │ ├── distributed/ │ │ ├── distributed.go │ │ └── distributed_test.go │ ├── logger/ │ │ ├── logger.go │ │ └── logger_test.go │ └── table/ │ ├── column.go │ ├── column_test.go │ ├── spec.go │ ├── spec_test.go │ ├── table.go │ └── table_test.go ├── server.go ├── server_test.go ├── traces/ │ ├── traces.go │ └── traces_test.go └── transport/ ├── doc.go ├── transport.go └── transport_windows.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/go.yml ================================================ name: Go on: workflow_dispatch: push: branches: [main, master] tags: '*' pull_request: branches: '**' jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: os: - ubuntu-latest - macos-latest steps: - uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v4 with: go-version-file: 'go.mod' - name: Test run: go test -v --race --cover ./... ================================================ FILE: .gitignore ================================================ # Example binaries example_* # Glide vendor directory vendor/ ## From https://github.com/github/gitignore/blob/master/Go.gitignore ## # Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ ================================================ FILE: CONTRIBUTING.md ================================================ ## Code of Conduct ### Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ### Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ### Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ### Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ### Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@kolide.co. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ### Attribution This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4. ================================================ FILE: LICENSE ================================================ MIT License Copyright 2017 Kolide Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ PATH := $(GOPATH)/bin:$(PATH) export GO111MODULE=on all: gen examples go-mod-check: @go help mod > /dev/null || (echo "Your go is too old, no modules. Seek help." && exit 1) go-mod-download: go mod download deps-go: go-mod-check go-mod-download deps: deps-go gen: ./osquery.thrift mkdir -p ./gen thrift --gen go:package_prefix=github.com/osquery/osquery-go/gen/ -out ./gen ./osquery.thrift rm -rf gen/osquery/extension-remote gen/osquery/extension_manager-remote gofmt -w ./gen examples: example_query example_call example_logger example_distributed example_table example_config example_query: examples/query/*.go go build -o example_query ./examples/query/*.go example_call: examples/call/*.go go build -o example_call ./examples/call/*.go example_logger: examples/logger/*.go go build -o example_logger.ext ./examples/logger/*.go example_distributed: examples/distributed/*.go go build -o example_distributed.ext ./examples/distributed/*.go example_table: examples/table/*.go go build -o example_table ./examples/table/*.go example_config: examples/config/*.go go build -o example_config ./examples/config/*.go test: all go test -race -cover ./... clean: rm -rf ./build ./gen .PHONY: all ================================================ FILE: README.md ================================================ # osquery-go [![GoDoc](https://godoc.org/github.com/osquery/osquery-go?status.svg)](http://godoc.org/github.com/osquery/osquery-go) [osquery](https://github.com/facebook/osquery) exposes an operating system as a high-performance relational database. This allows you to write SQL-based queries to explore operating system data. With osquery, SQL tables represent abstract concepts such as running processes, loaded kernel modules, open network connections, browser plugins, hardware events or file hashes. If you're interested in learning more about osquery, visit the [GitHub project](https://github.com/facebook/osquery), the [website](https://osquery.io), and the [users guide](https://osquery.readthedocs.io). ## What is osquery-go? In osquery, SQL tables, configuration retrieval, log handling, etc. are implemented via a robust plugin and extensions API. This project contains Go bindings for creating osquery extensions in Go. To create an extension, you must create an executable binary which instantiates an `ExtensionManagerServer` and registers the plugins that you would like to be added to osquery. You can then have osquery load the extension in your desired context (ie: in a long running instance of `osqueryd` or during an interactive query session with `osqueryi`). For more information about how this process works at a lower level, see the osquery [wiki](https://osquery.readthedocs.io/en/latest/development/osquery-sdk/). ## Install This library is compatible with Go Modules. To install: ``` go go get github.com/osquery/osquery-go ``` ## Using the library ### Creating a new osquery table If you want to create a custom osquery table in Go, you'll need to write an extension which registers the implementation of your table. Consider the following Go program: ```go package main import ( "context" "log" "os" "flag" "github.com/osquery/osquery-go" "github.com/osquery/osquery-go/plugin/table" ) func main() { socket := flag.String("socket", "", "Path to osquery socket file") flag.Parse() if *socket == "" { log.Fatalf(`Usage: %s --socket SOCKET_PATH`, os.Args[0]) } server, err := osquery.NewExtensionManagerServer("foobar", *socket) if err != nil { log.Fatalf("Error creating extension: %s\n", err) } // Create and register a new table plugin with the server. // table.NewPlugin requires the table plugin name, // a slice of Columns and a Generate function. server.RegisterPlugin(table.NewPlugin("foobar", FoobarColumns(), FoobarGenerate)) if err := server.Run(); err != nil { log.Fatalln(err) } } // FoobarColumns returns the columns that our table will return. func FoobarColumns() []table.ColumnDefinition { return []table.ColumnDefinition{ table.TextColumn("foo"), table.TextColumn("baz"), } } // FoobarGenerate will be called whenever the table is queried. It should return // a full table scan. func FoobarGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { return []map[string]string{ { "foo": "bar", "baz": "baz", }, { "foo": "bar", "baz": "baz", }, }, nil } ``` To test this code, start an osquery shell and find the path of the osquery extension socket: ```sql osqueryi --nodisable_extensions osquery> select value from osquery_flags where name = 'extensions_socket'; +-----------------------------------+ | value | +-----------------------------------+ | /Users/USERNAME/.osquery/shell.em | +-----------------------------------+ ``` Then start the Go extension and have it communicate with osqueryi via the extension socket that you retrieved above: ```bash go run ./my_table_plugin.go --socket /Users/USERNAME/.osquery/shell.em ``` Alternatively, you can also autoload your extension when starting an osquery shell: ```bash go build -o my_table_plugin my_table_plugin.go osqueryi --extension /path/to/my_table_plugin ``` This will register a table called "foobar". As you can see, the table will return two rows: ```sql osquery> select * from foobar; +-----+-----+ | foo | baz | +-----+-----+ | bar | baz | | bar | baz | +-----+-----+ osquery> ``` This is obviously a contrived example, but it's easy to imagine the possibilities. Using the instructions found on the [wiki](https://osquery.readthedocs.io/en/latest/development/osquery-sdk/), you can deploy your extension with an existing osquery deployment. ### Creating logger and config plugins The process required to create a config and/or logger plugin is very similar to the process outlined above for creating an osquery table. Specifically, you would create an `ExtensionManagerServer` instance in `func main()`, register your plugin and launch the extension as described above. The only difference is that the implementation of your plugin would be different. Each plugin package has a `NewPlugin` function which takes the plugin name as the first argument, followed by a list of required arguments to implement the plugin. For example, consider the implementation of an example logger plugin: ```go func main() { // create and register the plugin server.RegisterPlugin(logger.NewPlugin("example_logger", LogString)) } func LogString(ctx context.Context, typ logger.LogType, logText string) error { log.Printf("%s: %s\n", typ, logText) return nil } ``` Additionally, consider the implementation of an example config plugin: ```go func main() { // create and register the plugin server.RegisterPlugin(config.NewPlugin("example", GenerateConfigs)) } func GenerateConfigs(ctx context.Context) (map[string]string, error) { return map[string]string{ "config1": ` { "options": { "host_identifier": "hostname", "schedule_splay_percent": 10 }, "schedule": { "macos_kextstat": { "query": "SELECT * FROM kernel_extensions;", "interval": 10 }, "foobar": { "query": "SELECT foo, bar, pid FROM foobar_table;", "interval": 600 } } } `, }, nil } ``` All of these examples and more can be found in the [examples](./examples) subdirectory of this repository. ### Execute queries in Go This library can also be used to create a Go client for the osqueryd or osqueryi's extension socket. You can use this to add the ability to performantly execute osquery queries to your Go program. Consider the following example: ```go package main import ( "fmt" "os" "time" "github.com/osquery/osquery-go" ) func main() { if len(os.Args) != 3 { log.Fatalf("Usage: %s SOCKET_PATH QUERY", os.Args[0]) } client, err := osquery.NewClient(os.Args[1], 10*time.Second) if err != nil { log.Fatalf("Error creating Thrift client: %v", err) } defer client.Close() resp, err := client.Query(os.Args[2]) if err != nil { log.Fatalf("Error communicating with osqueryd: %v",err) } if resp.Status.Code != 0 { log.Fatalf("osqueryd returned error: %s", resp.Status.Message) } fmt.Printf("Got results:\n%#v\n", resp.Response) } ``` ### Loading extensions with osqueryd If you write an extension with a logger or config plugin, you'll likely want to autoload the extensions when `osqueryd` starts. `osqueryd` has a few requirements for autoloading extensions, documented on the [wiki](https://osquery.readthedocs.io/en/latest/deployment/extensions/). Here's a quick example using a logging plugin to get you started: 1. Build the plugin. Make sure to add `.ext` as the file extension. It is required by osqueryd. ```go build -o /usr/local/osquery_extensions/my_logger.ext``` 2. Set the correct permissions on the file and directory. If `osqueryd` runs as root, the directory for the extension must only be writable by root. ``` sudo chown -R root /usr/local/osquery_extensions/ ``` 3. Create an `extensions.load` file with the path of your extension. ``` echo "/usr/local/osquery_extensions/my_logger.ext" > /tmp/extensions.load ``` 4. Start `osqueryd` with the `--extensions_autoload` flag. ``` sudo osqueryd --extensions_autoload=/tmp/extensions.load --logger-plugin=my_logger -verbose ``` ## Contributing For more information on contributing to this project, see [CONTRIBUTING.md](./CONTRIBUTING.md). ## Vulnerabilities If you find a vulnerability in this software, please email [security@kolide.co](mailto:security@kolide.co). ================================================ FILE: client.go ================================================ package osquery import ( "context" "time" "github.com/osquery/osquery-go/gen/osquery" "github.com/osquery/osquery-go/traces" "github.com/osquery/osquery-go/transport" "github.com/apache/thrift/lib/go/thrift" "github.com/pkg/errors" ) const ( defaultWaitTime = 200 * time.Millisecond defaultMaxWaitTime = 1 * time.Minute ) // ExtensionManagerClient is a wrapper for the osquery Thrift extensions API. type ExtensionManagerClient struct { client osquery.ExtensionManager transport thrift.TTransport waitTime time.Duration maxWaitTime time.Duration lock *locker } type ClientOption func(*ExtensionManagerClient) // WaitTime sets the default amount of wait time for the osquery socket to free up. You can override this on a per // call basis by setting a context deadline func DefaultWaitTime(d time.Duration) ClientOption { return func(c *ExtensionManagerClient) { c.waitTime = d } } // MaxWaitTime is the maximum amount of time something is allowed to wait for the osquery socket. This takes precedence // over the context deadline. func MaxWaitTime(d time.Duration) ClientOption { return func(c *ExtensionManagerClient) { c.maxWaitTime = d } } // NewClient creates a new client communicating to osquery over the socket at // the provided path. If resolving the address or connecting to the socket // fails, this function will error. func NewClient(path string, socketOpenTimeout time.Duration, opts ...ClientOption) (*ExtensionManagerClient, error) { c := &ExtensionManagerClient{ waitTime: defaultWaitTime, maxWaitTime: defaultMaxWaitTime, } for _, opt := range opts { opt(c) } if c.waitTime > c.maxWaitTime { return nil, errors.New("default wait time larger than max wait time") } c.lock = NewLocker(c.waitTime, c.maxWaitTime) if c.client == nil { trans, err := transport.Open(path, socketOpenTimeout) if err != nil { return nil, err } c.transport = trans c.client = osquery.NewExtensionManagerClientFactory( trans, thrift.NewTBinaryProtocolFactoryDefault(), ) } return c, nil } // Close should be called to close the transport when use of the client is // completed. func (c *ExtensionManagerClient) Close() { if c.transport != nil && c.transport.IsOpen() { c.transport.Close() } } // Ping requests metadata from the extension manager, using a new background context func (c *ExtensionManagerClient) Ping() (*osquery.ExtensionStatus, error) { return c.PingContext(context.Background()) } // PingContext requests metadata from the extension manager. func (c *ExtensionManagerClient) PingContext(ctx context.Context) (*osquery.ExtensionStatus, error) { if err := c.lock.Lock(ctx); err != nil { return nil, err } defer c.lock.Unlock() return c.client.Ping(ctx) } // Call requests a call to an extension (or core) registry plugin, using a new background context func (c *ExtensionManagerClient) Call(registry, item string, request osquery.ExtensionPluginRequest) (*osquery.ExtensionResponse, error) { return c.CallContext(context.Background(), registry, item, request) } // CallContext requests a call to an extension (or core) registry plugin. func (c *ExtensionManagerClient) CallContext(ctx context.Context, registry, item string, request osquery.ExtensionPluginRequest) (*osquery.ExtensionResponse, error) { ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.CallContext") defer span.End() if err := c.lock.Lock(ctx); err != nil { return nil, err } defer c.lock.Unlock() return c.client.Call(ctx, registry, item, request) } // Extensions requests the list of active registered extensions, using a new background context func (c *ExtensionManagerClient) Extensions() (osquery.InternalExtensionList, error) { return c.ExtensionsContext(context.Background()) } // ExtensionsContext requests the list of active registered extensions. func (c *ExtensionManagerClient) ExtensionsContext(ctx context.Context) (osquery.InternalExtensionList, error) { ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.ExtensionsContext") defer span.End() if err := c.lock.Lock(ctx); err != nil { return nil, err } defer c.lock.Unlock() return c.client.Extensions(ctx) } // RegisterExtension registers the extension plugins with the osquery process, using a new background context func (c *ExtensionManagerClient) RegisterExtension(info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) { return c.RegisterExtensionContext(context.Background(), info, registry) } // RegisterExtensionContext registers the extension plugins with the osquery process. func (c *ExtensionManagerClient) RegisterExtensionContext(ctx context.Context, info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) { ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.RegisterExtensionContext") defer span.End() if err := c.lock.Lock(ctx); err != nil { return nil, err } defer c.lock.Unlock() return c.client.RegisterExtension(ctx, info, registry) } // DeregisterExtension de-registers the extension plugins with the osquery process, using a new background context func (c *ExtensionManagerClient) DeregisterExtension(uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) { return c.DeregisterExtensionContext(context.Background(), uuid) } // DeregisterExtensionContext de-registers the extension plugins with the osquery process. func (c *ExtensionManagerClient) DeregisterExtensionContext(ctx context.Context, uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) { ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.DeregisterExtensionContext") defer span.End() if err := c.lock.Lock(ctx); err != nil { return nil, err } defer c.lock.Unlock() return c.client.DeregisterExtension(ctx, uuid) } // Options requests the list of bootstrap or configuration options, using a new background context. func (c *ExtensionManagerClient) Options() (osquery.InternalOptionList, error) { return c.OptionsContext(context.Background()) } // OptionsContext requests the list of bootstrap or configuration options. func (c *ExtensionManagerClient) OptionsContext(ctx context.Context) (osquery.InternalOptionList, error) { ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.OptionsContext") defer span.End() if err := c.lock.Lock(ctx); err != nil { return nil, err } defer c.lock.Unlock() return c.client.Options(ctx) } // Query requests a query to be run and returns the extension // response, using a new background context. Consider using the // QueryRow or QueryRows helpers for a more friendly interface. func (c *ExtensionManagerClient) Query(sql string) (*osquery.ExtensionResponse, error) { return c.QueryContext(context.Background(), sql) } // QueryContext requests a query to be run and returns the extension response. // Consider using the QueryRow or QueryRows helpers for a more friendly // interface. func (c *ExtensionManagerClient) QueryContext(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) { ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.QueryContext") defer span.End() if err := c.lock.Lock(ctx); err != nil { return nil, err } defer c.lock.Unlock() return c.client.Query(ctx, sql) } // QueryRows is a helper that executes the requested query and returns the // results. It handles checking both the transport level errors and the osquery // internal errors by returning a normal Go error type. func (c *ExtensionManagerClient) QueryRows(sql string) ([]map[string]string, error) { return c.QueryRowsContext(context.Background(), sql) } // QueryRowsContext is a helper that executes the requested query and returns the // results. It handles checking both the transport level errors and the osquery // internal errors by returning a normal Go error type. func (c *ExtensionManagerClient) QueryRowsContext(ctx context.Context, sql string) ([]map[string]string, error) { ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.QueryRowsContext") defer span.End() res, err := c.QueryContext(ctx, sql) if err != nil { return nil, errors.Wrap(err, "transport error in query") } if res.Status == nil { return nil, errors.New("query returned nil status") } if res.Status.Code != 0 { return nil, errors.Errorf("query returned error: %s", res.Status.Message) } return res.Response, nil } // QueryRow behaves similarly to QueryRows, but it returns an error if the // query does not return exactly one row. func (c *ExtensionManagerClient) QueryRow(sql string) (map[string]string, error) { return c.QueryRowContext(context.Background(), sql) } // QueryRowContext behaves similarly to QueryRows, but it returns an error if the // query does not return exactly one row. func (c *ExtensionManagerClient) QueryRowContext(ctx context.Context, sql string) (map[string]string, error) { ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.QueryRowContext") defer span.End() res, err := c.QueryRowsContext(ctx, sql) if err != nil { return nil, err } if len(res) != 1 { return nil, errors.Errorf("expected 1 row, got %d", len(res)) } return res[0], nil } // GetQueryColumns requests the columns returned by the parsed query, using a new background context. func (c *ExtensionManagerClient) GetQueryColumns(sql string) (*osquery.ExtensionResponse, error) { return c.GetQueryColumnsContext(context.Background(), sql) } // GetQueryColumnsContext requests the columns returned by the parsed query. func (c *ExtensionManagerClient) GetQueryColumnsContext(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) { ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.GetQueryColumnsContext") defer span.End() if err := c.lock.Lock(ctx); err != nil { return nil, err } defer c.lock.Unlock() return c.client.GetQueryColumns(ctx, sql) } ================================================ FILE: client_test.go ================================================ package osquery import ( "context" "errors" "fmt" "os" "sync" "testing" "time" "github.com/osquery/osquery-go/gen/osquery" "github.com/osquery/osquery-go/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestQueryRows(t *testing.T) { t.Parallel() mock := &mock.ExtensionManager{} client, err := NewClient("", 5*time.Second, WithOsqueryThriftClient(mock)) require.NoError(t, err) // Transport related error mock.QueryFunc = func(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) { return nil, errors.New("boom!") } rows, err := client.QueryRows("select 1") assert.NotNil(t, err) row, err := client.QueryRow("select 1") assert.NotNil(t, err) // Nil status mock.QueryFunc = func(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) { return &osquery.ExtensionResponse{}, nil } rows, err = client.QueryRows("select 1") assert.NotNil(t, err) row, err = client.QueryRow("select 1") assert.NotNil(t, err) // Query error mock.QueryFunc = func(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) { return &osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{Code: 1, Message: "bad query"}, }, nil } rows, err = client.QueryRows("select bad query") assert.NotNil(t, err) row, err = client.QueryRow("select bad query") assert.NotNil(t, err) // Good query (one row) expectedRows := []map[string]string{ {"1": "1"}, } mock.QueryFunc = func(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) { return &osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{Code: 0, Message: "OK"}, Response: expectedRows, }, nil } rows, err = client.QueryRows("select 1") assert.Nil(t, err) assert.Equal(t, expectedRows, rows) row, err = client.QueryRow("select 1") assert.Nil(t, err) assert.Equal(t, expectedRows[0], row) // Good query (multiple rows) expectedRows = []map[string]string{ {"1": "1"}, {"1": "2"}, } mock.QueryFunc = func(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) { return &osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{Code: 0, Message: "OK"}, Response: expectedRows, }, nil } rows, err = client.QueryRows("select 1 union select 2") assert.Nil(t, err) assert.Equal(t, expectedRows, rows) row, err = client.QueryRow("select 1 union select 2") assert.NotNil(t, err) } // TestLocking tests the the client correctly locks access to the osquery socket. Thrift only supports a single // actor on the socket at a time, this means that in parallel go code, it's very easy to have messages get // crossed and generate errors. This tests to ensure the locking works func TestLocking(t *testing.T) { t.Parallel() sock := os.Getenv("OSQ_SOCKET") if sock == "" { t.Skip("no osquery socket specified") } osq, err := NewClient(sock, 5*time.Second) require.NoError(t, err) // The issue we're testing is about multithreaded access. Let's hammer on it! wait := sync.WaitGroup{} for i := 0; i < 100; i++ { wait.Add(1) go func() { defer wait.Done() status, err := osq.Ping() require.NoError(t, err, "call to Ping()") if err != nil { require.Equal(t, 0, status.Code, fmt.Errorf("ping returned %d: %s", status.Code, status.Message)) } }() } wait.Wait() } func TestLockTimeouts(t *testing.T) { t.Parallel() mock := &mock.ExtensionManager{} client, err := NewClient("", 5*time.Second, WithOsqueryThriftClient(mock), DefaultWaitTime(100*time.Millisecond), DefaultWaitTime(5*time.Second)) require.NoError(t, err) wait := sync.WaitGroup{} errChan := make(chan error, 10) for i := 0; i < 3; i++ { wait.Add(1) go func() { defer wait.Done() ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) defer cancel() errChan <- client.SlowLocker(ctx, 75*time.Millisecond) }() } wait.Wait() close(errChan) var successCount, errCount int for err := range errChan { if err == nil { successCount += 1 } else { errCount += 1 } } assert.Equal(t, 2, successCount, "expected success count") assert.Equal(t, 1, errCount, "expected error count") } // WithOsqueryThriftClient sets the underlying thrift client. This can be used to set a mock func WithOsqueryThriftClient(client osquery.ExtensionManager) ClientOption { return func(c *ExtensionManagerClient) { c.client = client } } // SlowLocker attempts to emulate a slow sql routine, so we can test how lock timeouts work. func (c *ExtensionManagerClient) SlowLocker(ctx context.Context, d time.Duration) error { if err := c.lock.Lock(ctx); err != nil { return err } defer c.lock.Unlock() time.Sleep(d) return nil } ================================================ FILE: examples/call/main.go ================================================ package main import ( "fmt" "os" "time" "github.com/osquery/osquery-go" ) func main() { if len(os.Args) != 5 { fmt.Printf(`Usage: %s SOCKET_PATH REGISTRY_NAME PLUGIN_NAME ACTION Calls the provided action for the plugin with the given registry and plugin name. `, os.Args[0]) os.Exit(1) } socketPath := os.Args[1] registryName := os.Args[2] pluginName := os.Args[3] action := os.Args[4] client, err := osquery.NewClient(socketPath, 10*time.Second) if err != nil { fmt.Println("Error creating Thrift client: " + err.Error()) os.Exit(1) } defer client.Close() resp, err := client.Call(registryName, pluginName, map[string]string{"action": action}) if err != nil { fmt.Println("Error communicating with osqueryd: " + err.Error()) os.Exit(1) } if resp.Status.Code != 0 { fmt.Println("osqueryd returned error: " + resp.Status.Message) os.Exit(1) } fmt.Printf("Got results:\n%#v\n", resp.Response) } ================================================ FILE: examples/config/main.go ================================================ package main import ( "context" "flag" "log" "time" "github.com/osquery/osquery-go" "github.com/osquery/osquery-go/plugin/config" ) var ( socket = flag.String("socket", "", "Path to the extensions UNIX domain socket") timeout = flag.Int("timeout", 3, "Seconds to wait for autoloaded extensions") interval = flag.Int("interval", 3, "Seconds delay between connectivity checks") ) func main() { flag.Parse() if *socket == "" { log.Fatalln("Missing required --socket argument") } serverTimeout := osquery.ServerTimeout( time.Second * time.Duration(*timeout), ) serverPingInterval := osquery.ServerPingInterval( time.Second * time.Duration(*interval), ) server, err := osquery.NewExtensionManagerServer( "example_extension", *socket, serverTimeout, serverPingInterval, ) if err != nil { log.Fatalf("Error creating extension: %s\n", err) } server.RegisterPlugin(config.NewPlugin("example_config", GenerateConfigs)) if err := server.Run(); err != nil { log.Fatal(err) } } func GenerateConfigs(ctx context.Context) (map[string]string, error) { return map[string]string{ "config1": ` { "options": { "host_identifier": "hostname", "schedule_splay_percent": 10 }, "schedule": { "macos_kextstat": { "query": "SELECT * FROM kernel_extensions;", "interval": 10 }, "foobar": { "query": "SELECT foo, bar, pid FROM foobar_table;", "interval": 600 } } } `, }, nil } ================================================ FILE: examples/distributed/main.go ================================================ package main import ( "context" "flag" "fmt" "log" "github.com/osquery/osquery-go" "github.com/osquery/osquery-go/plugin/distributed" ) func main() { socketPath := flag.String("socket", "", "path to osqueryd extensions socket") flag.Int("timeout", 0, "") flag.Int("interval", 0, "") flag.Parse() server, err := osquery.NewExtensionManagerServer("example_distributed", *socketPath) if err != nil { log.Fatalf("Error creating extension: %s\n", err) } server.RegisterPlugin(distributed.NewPlugin("example_distributed", getQueries, writeResults)) if err := server.Run(); err != nil { log.Fatal(err) } } func getQueries(ctx context.Context) (*distributed.GetQueriesResult, error) { return &distributed.GetQueriesResult{Queries: map[string]string{"time": "select * from time"}}, nil } func writeResults(ctx context.Context, results []distributed.Result) error { fmt.Println(results) return nil } ================================================ FILE: examples/logger/main.go ================================================ package main import ( "context" "flag" "log" "github.com/osquery/osquery-go" "github.com/osquery/osquery-go/plugin/logger" ) func main() { socketPath := flag.String("socket", "", "path to osqueryd extensions socket") flag.Int("timeout", 0, "") flag.Int("interval", 0, "") flag.Parse() server, err := osquery.NewExtensionManagerServer("example_logger", *socketPath) if err != nil { log.Fatalf("Error creating extension: %s\n", err) } server.RegisterPlugin(logger.NewPlugin("example_logger", LogString)) if err := server.Run(); err != nil { log.Fatal(err) } } func LogString(ctx context.Context, typ logger.LogType, logText string) error { log.Printf("%s: %s\n", typ, logText) return nil } ================================================ FILE: examples/query/main.go ================================================ package main import ( "fmt" "os" "time" "github.com/osquery/osquery-go" ) func main() { if len(os.Args) != 3 { fmt.Printf(`Usage: %s SOCKET_PATH QUERY\n Requests osqueryd to run the provided query and prints the results. `, os.Args[0]) os.Exit(1) } client, err := osquery.NewClient(os.Args[1], 10*time.Second) if err != nil { fmt.Println("Error creating Thrift client: " + err.Error()) os.Exit(1) } defer client.Close() resp, err := client.Query(os.Args[2]) if err != nil { fmt.Println("Error communicating with osqueryd: " + err.Error()) os.Exit(1) } if resp.Status.Code != 0 { fmt.Println("osqueryd returned error: " + resp.Status.Message) os.Exit(1) } fmt.Printf("Got results:\n%#v\n", resp.Response) } ================================================ FILE: examples/table/main.go ================================================ package main import ( "context" "encoding/json" "flag" "fmt" "log" "os" "time" "github.com/osquery/osquery-go" "github.com/osquery/osquery-go/plugin/table" ) var ( socket = flag.String("socket", "", "Path to the extensions UNIX domain socket") timeout = flag.Int("timeout", 3, "Seconds to wait for autoloaded extensions") interval = flag.Int("interval", 3, "Seconds delay between connectivity checks") spec = flag.Bool("spec", false, "Don't run as a plugin, instead print the table spec") ) func main() { flag.Parse() tbl := table.NewPlugin("example_table", ExampleColumns(), ExampleGenerate, table.WithDescription("A simple example table")) if *spec { tableSpec, err := json.MarshalIndent(tbl.Spec(), "", " ") if err != nil { log.Fatalf("Error marshalling spec: %s\n", err) } fmt.Printf("%s\n", tableSpec) os.Exit(0) } if *socket == "" { log.Fatalln("Missing required --socket argument") } serverTimeout := osquery.ServerTimeout( time.Second * time.Duration(*timeout), ) serverPingInterval := osquery.ServerPingInterval( time.Second * time.Duration(*interval), ) server, err := osquery.NewExtensionManagerServer( "example_extension", *socket, serverTimeout, serverPingInterval, ) if err != nil { log.Fatalf("Error creating extension: %s\n", err) } server.RegisterPlugin(tbl) if err := server.Run(); err != nil { log.Fatal(err) } } func ExampleColumns() []table.ColumnDefinition { return []table.ColumnDefinition{ table.TextColumn("text", table.ColumnDescription("Some text")), table.IntegerColumn("integer"), table.BigIntColumn("big_int"), table.DoubleColumn("double"), } } func ExampleGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { return []map[string]string{ { "text": "hello world", "integer": "123", "big_int": "-1234567890", "double": "3.14159", }, }, nil } ================================================ FILE: gen/osquery/GoUnusedProtection__.go ================================================ // Code generated by Thrift Compiler (0.16.0). DO NOT EDIT. package osquery var GoUnusedProtection__ int ================================================ FILE: gen/osquery/osquery-consts.go ================================================ // Code generated by Thrift Compiler (0.16.0). DO NOT EDIT. package osquery import ( "bytes" "context" "fmt" thrift "github.com/apache/thrift/lib/go/thrift" "time" ) // (needed to ensure safety because of naive import list construction.) var _ = thrift.ZERO var _ = fmt.Printf var _ = context.Background var _ = time.Now var _ = bytes.Equal func init() { } ================================================ FILE: gen/osquery/osquery.go ================================================ // Code generated by Thrift Compiler (0.16.0). DO NOT EDIT. package osquery import ( "bytes" "context" "database/sql/driver" "errors" "fmt" thrift "github.com/apache/thrift/lib/go/thrift" "time" ) // (needed to ensure safety because of naive import list construction.) var _ = thrift.ZERO var _ = fmt.Printf var _ = context.Background var _ = time.Now var _ = bytes.Equal type ExtensionCode int64 const ( ExtensionCode_EXT_SUCCESS ExtensionCode = 0 ExtensionCode_EXT_FAILED ExtensionCode = 1 ExtensionCode_EXT_FATAL ExtensionCode = 2 ) func (p ExtensionCode) String() string { switch p { case ExtensionCode_EXT_SUCCESS: return "EXT_SUCCESS" case ExtensionCode_EXT_FAILED: return "EXT_FAILED" case ExtensionCode_EXT_FATAL: return "EXT_FATAL" } return "" } func ExtensionCodeFromString(s string) (ExtensionCode, error) { switch s { case "EXT_SUCCESS": return ExtensionCode_EXT_SUCCESS, nil case "EXT_FAILED": return ExtensionCode_EXT_FAILED, nil case "EXT_FATAL": return ExtensionCode_EXT_FATAL, nil } return ExtensionCode(0), fmt.Errorf("not a valid ExtensionCode string") } func ExtensionCodePtr(v ExtensionCode) *ExtensionCode { return &v } func (p ExtensionCode) MarshalText() ([]byte, error) { return []byte(p.String()), nil } func (p *ExtensionCode) UnmarshalText(text []byte) error { q, err := ExtensionCodeFromString(string(text)) if err != nil { return err } *p = q return nil } func (p *ExtensionCode) Scan(value interface{}) error { v, ok := value.(int64) if !ok { return errors.New("Scan value is not int64") } *p = ExtensionCode(v) return nil } func (p *ExtensionCode) Value() (driver.Value, error) { if p == nil { return nil, nil } return int64(*p), nil } type ExtensionPluginRequest map[string]string func ExtensionPluginRequestPtr(v ExtensionPluginRequest) *ExtensionPluginRequest { return &v } type ExtensionPluginResponse []map[string]string func ExtensionPluginResponsePtr(v ExtensionPluginResponse) *ExtensionPluginResponse { return &v } type InternalOptionList map[string]*InternalOptionInfo func InternalOptionListPtr(v InternalOptionList) *InternalOptionList { return &v } type ExtensionRouteUUID int64 func ExtensionRouteUUIDPtr(v ExtensionRouteUUID) *ExtensionRouteUUID { return &v } type ExtensionRouteTable map[string]ExtensionPluginResponse func ExtensionRouteTablePtr(v ExtensionRouteTable) *ExtensionRouteTable { return &v } type ExtensionRegistry map[string]ExtensionRouteTable func ExtensionRegistryPtr(v ExtensionRegistry) *ExtensionRegistry { return &v } type InternalExtensionList map[ExtensionRouteUUID]*InternalExtensionInfo func InternalExtensionListPtr(v InternalExtensionList) *InternalExtensionList { return &v } // Attributes: // - Value // - DefaultValue // - Type type InternalOptionInfo struct { Value string `thrift:"value,1" db:"value" json:"value"` DefaultValue string `thrift:"default_value,2" db:"default_value" json:"default_value"` Type string `thrift:"type,3" db:"type" json:"type"` } func NewInternalOptionInfo() *InternalOptionInfo { return &InternalOptionInfo{} } func (p *InternalOptionInfo) GetValue() string { return p.Value } func (p *InternalOptionInfo) GetDefaultValue() string { return p.DefaultValue } func (p *InternalOptionInfo) GetType() string { return p.Type } func (p *InternalOptionInfo) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 1: if fieldTypeId == thrift.STRING { if err := p.ReadField1(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 2: if fieldTypeId == thrift.STRING { if err := p.ReadField2(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 3: if fieldTypeId == thrift.STRING { if err := p.ReadField3(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *InternalOptionInfo) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 1: ", err) } else { p.Value = v } return nil } func (p *InternalOptionInfo) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 2: ", err) } else { p.DefaultValue = v } return nil } func (p *InternalOptionInfo) ReadField3(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 3: ", err) } else { p.Type = v } return nil } func (p *InternalOptionInfo) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "InternalOptionInfo"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField1(ctx, oprot); err != nil { return err } if err := p.writeField2(ctx, oprot); err != nil { return err } if err := p.writeField3(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *InternalOptionInfo) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "value", thrift.STRING, 1); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:value: ", p), err) } if err := oprot.WriteString(ctx, string(p.Value)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.value (1) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 1:value: ", p), err) } return err } func (p *InternalOptionInfo) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "default_value", thrift.STRING, 2); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:default_value: ", p), err) } if err := oprot.WriteString(ctx, string(p.DefaultValue)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.default_value (2) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 2:default_value: ", p), err) } return err } func (p *InternalOptionInfo) writeField3(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "type", thrift.STRING, 3); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 3:type: ", p), err) } if err := oprot.WriteString(ctx, string(p.Type)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.type (3) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 3:type: ", p), err) } return err } func (p *InternalOptionInfo) Equals(other *InternalOptionInfo) bool { if p == other { return true } else if p == nil || other == nil { return false } if p.Value != other.Value { return false } if p.DefaultValue != other.DefaultValue { return false } if p.Type != other.Type { return false } return true } func (p *InternalOptionInfo) String() string { if p == nil { return "" } return fmt.Sprintf("InternalOptionInfo(%+v)", *p) } // Attributes: // - Name // - Version // - SdkVersion // - MinSdkVersion type InternalExtensionInfo struct { Name string `thrift:"name,1" db:"name" json:"name"` Version string `thrift:"version,2" db:"version" json:"version"` SdkVersion string `thrift:"sdk_version,3" db:"sdk_version" json:"sdk_version"` MinSdkVersion string `thrift:"min_sdk_version,4" db:"min_sdk_version" json:"min_sdk_version"` } func NewInternalExtensionInfo() *InternalExtensionInfo { return &InternalExtensionInfo{} } func (p *InternalExtensionInfo) GetName() string { return p.Name } func (p *InternalExtensionInfo) GetVersion() string { return p.Version } func (p *InternalExtensionInfo) GetSdkVersion() string { return p.SdkVersion } func (p *InternalExtensionInfo) GetMinSdkVersion() string { return p.MinSdkVersion } func (p *InternalExtensionInfo) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 1: if fieldTypeId == thrift.STRING { if err := p.ReadField1(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 2: if fieldTypeId == thrift.STRING { if err := p.ReadField2(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 3: if fieldTypeId == thrift.STRING { if err := p.ReadField3(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 4: if fieldTypeId == thrift.STRING { if err := p.ReadField4(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *InternalExtensionInfo) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 1: ", err) } else { p.Name = v } return nil } func (p *InternalExtensionInfo) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 2: ", err) } else { p.Version = v } return nil } func (p *InternalExtensionInfo) ReadField3(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 3: ", err) } else { p.SdkVersion = v } return nil } func (p *InternalExtensionInfo) ReadField4(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 4: ", err) } else { p.MinSdkVersion = v } return nil } func (p *InternalExtensionInfo) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "InternalExtensionInfo"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField1(ctx, oprot); err != nil { return err } if err := p.writeField2(ctx, oprot); err != nil { return err } if err := p.writeField3(ctx, oprot); err != nil { return err } if err := p.writeField4(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *InternalExtensionInfo) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "name", thrift.STRING, 1); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:name: ", p), err) } if err := oprot.WriteString(ctx, string(p.Name)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.name (1) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 1:name: ", p), err) } return err } func (p *InternalExtensionInfo) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "version", thrift.STRING, 2); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:version: ", p), err) } if err := oprot.WriteString(ctx, string(p.Version)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.version (2) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 2:version: ", p), err) } return err } func (p *InternalExtensionInfo) writeField3(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "sdk_version", thrift.STRING, 3); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 3:sdk_version: ", p), err) } if err := oprot.WriteString(ctx, string(p.SdkVersion)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.sdk_version (3) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 3:sdk_version: ", p), err) } return err } func (p *InternalExtensionInfo) writeField4(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "min_sdk_version", thrift.STRING, 4); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 4:min_sdk_version: ", p), err) } if err := oprot.WriteString(ctx, string(p.MinSdkVersion)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.min_sdk_version (4) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 4:min_sdk_version: ", p), err) } return err } func (p *InternalExtensionInfo) Equals(other *InternalExtensionInfo) bool { if p == other { return true } else if p == nil || other == nil { return false } if p.Name != other.Name { return false } if p.Version != other.Version { return false } if p.SdkVersion != other.SdkVersion { return false } if p.MinSdkVersion != other.MinSdkVersion { return false } return true } func (p *InternalExtensionInfo) String() string { if p == nil { return "" } return fmt.Sprintf("InternalExtensionInfo(%+v)", *p) } // Attributes: // - Code // - Message // - UUID type ExtensionStatus struct { Code int32 `thrift:"code,1" db:"code" json:"code"` Message string `thrift:"message,2" db:"message" json:"message"` UUID ExtensionRouteUUID `thrift:"uuid,3" db:"uuid" json:"uuid"` } func NewExtensionStatus() *ExtensionStatus { return &ExtensionStatus{} } func (p *ExtensionStatus) GetCode() int32 { return p.Code } func (p *ExtensionStatus) GetMessage() string { return p.Message } func (p *ExtensionStatus) GetUUID() ExtensionRouteUUID { return p.UUID } func (p *ExtensionStatus) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 1: if fieldTypeId == thrift.I32 { if err := p.ReadField1(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 2: if fieldTypeId == thrift.STRING { if err := p.ReadField2(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 3: if fieldTypeId == thrift.I64 { if err := p.ReadField3(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionStatus) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadI32(ctx); err != nil { return thrift.PrependError("error reading field 1: ", err) } else { p.Code = v } return nil } func (p *ExtensionStatus) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 2: ", err) } else { p.Message = v } return nil } func (p *ExtensionStatus) ReadField3(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadI64(ctx); err != nil { return thrift.PrependError("error reading field 3: ", err) } else { temp := ExtensionRouteUUID(v) p.UUID = temp } return nil } func (p *ExtensionStatus) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "ExtensionStatus"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField1(ctx, oprot); err != nil { return err } if err := p.writeField2(ctx, oprot); err != nil { return err } if err := p.writeField3(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionStatus) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "code", thrift.I32, 1); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:code: ", p), err) } if err := oprot.WriteI32(ctx, int32(p.Code)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.code (1) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 1:code: ", p), err) } return err } func (p *ExtensionStatus) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "message", thrift.STRING, 2); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:message: ", p), err) } if err := oprot.WriteString(ctx, string(p.Message)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.message (2) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 2:message: ", p), err) } return err } func (p *ExtensionStatus) writeField3(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "uuid", thrift.I64, 3); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 3:uuid: ", p), err) } if err := oprot.WriteI64(ctx, int64(p.UUID)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.uuid (3) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 3:uuid: ", p), err) } return err } func (p *ExtensionStatus) Equals(other *ExtensionStatus) bool { if p == other { return true } else if p == nil || other == nil { return false } if p.Code != other.Code { return false } if p.Message != other.Message { return false } if p.UUID != other.UUID { return false } return true } func (p *ExtensionStatus) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionStatus(%+v)", *p) } // Attributes: // - Status // - Response type ExtensionResponse struct { Status *ExtensionStatus `thrift:"status,1" db:"status" json:"status"` Response ExtensionPluginResponse `thrift:"response,2" db:"response" json:"response"` } func NewExtensionResponse() *ExtensionResponse { return &ExtensionResponse{} } var ExtensionResponse_Status_DEFAULT *ExtensionStatus func (p *ExtensionResponse) GetStatus() *ExtensionStatus { if !p.IsSetStatus() { return ExtensionResponse_Status_DEFAULT } return p.Status } func (p *ExtensionResponse) GetResponse() ExtensionPluginResponse { return p.Response } func (p *ExtensionResponse) IsSetStatus() bool { return p.Status != nil } func (p *ExtensionResponse) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 1: if fieldTypeId == thrift.STRUCT { if err := p.ReadField1(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 2: if fieldTypeId == thrift.LIST { if err := p.ReadField2(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionResponse) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { p.Status = &ExtensionStatus{} if err := p.Status.Read(ctx, iprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Status), err) } return nil } func (p *ExtensionResponse) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { _, size, err := iprot.ReadListBegin(ctx) if err != nil { return thrift.PrependError("error reading list begin: ", err) } tSlice := make(ExtensionPluginResponse, 0, size) p.Response = tSlice for i := 0; i < size; i++ { _, _, size, err := iprot.ReadMapBegin(ctx) if err != nil { return thrift.PrependError("error reading map begin: ", err) } tMap := make(map[string]string, size) _elem0 := tMap for i := 0; i < size; i++ { var _key1 string if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 0: ", err) } else { _key1 = v } var _val2 string if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 0: ", err) } else { _val2 = v } _elem0[_key1] = _val2 } if err := iprot.ReadMapEnd(ctx); err != nil { return thrift.PrependError("error reading map end: ", err) } p.Response = append(p.Response, _elem0) } if err := iprot.ReadListEnd(ctx); err != nil { return thrift.PrependError("error reading list end: ", err) } return nil } func (p *ExtensionResponse) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "ExtensionResponse"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField1(ctx, oprot); err != nil { return err } if err := p.writeField2(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionResponse) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "status", thrift.STRUCT, 1); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:status: ", p), err) } if err := p.Status.Write(ctx, oprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Status), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 1:status: ", p), err) } return err } func (p *ExtensionResponse) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "response", thrift.LIST, 2); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:response: ", p), err) } if err := oprot.WriteListBegin(ctx, thrift.MAP, len(p.Response)); err != nil { return thrift.PrependError("error writing list begin: ", err) } for _, v := range p.Response { if err := oprot.WriteMapBegin(ctx, thrift.STRING, thrift.STRING, len(v)); err != nil { return thrift.PrependError("error writing map begin: ", err) } for k, v := range v { if err := oprot.WriteString(ctx, string(k)); err != nil { return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) } if err := oprot.WriteString(ctx, string(v)); err != nil { return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) } } if err := oprot.WriteMapEnd(ctx); err != nil { return thrift.PrependError("error writing map end: ", err) } } if err := oprot.WriteListEnd(ctx); err != nil { return thrift.PrependError("error writing list end: ", err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 2:response: ", p), err) } return err } func (p *ExtensionResponse) Equals(other *ExtensionResponse) bool { if p == other { return true } else if p == nil || other == nil { return false } if !p.Status.Equals(other.Status) { return false } if len(p.Response) != len(other.Response) { return false } for i, _tgt := range p.Response { _src3 := other.Response[i] if len(_tgt) != len(_src3) { return false } for k, _tgt := range _tgt { _src4 := _src3[k] if _tgt != _src4 { return false } } } return true } func (p *ExtensionResponse) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionResponse(%+v)", *p) } // Attributes: // - Code // - Message // - UUID type ExtensionException struct { Code int32 `thrift:"code,1" db:"code" json:"code"` Message string `thrift:"message,2" db:"message" json:"message"` UUID ExtensionRouteUUID `thrift:"uuid,3" db:"uuid" json:"uuid"` } func NewExtensionException() *ExtensionException { return &ExtensionException{} } func (p *ExtensionException) GetCode() int32 { return p.Code } func (p *ExtensionException) GetMessage() string { return p.Message } func (p *ExtensionException) GetUUID() ExtensionRouteUUID { return p.UUID } func (p *ExtensionException) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 1: if fieldTypeId == thrift.I32 { if err := p.ReadField1(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 2: if fieldTypeId == thrift.STRING { if err := p.ReadField2(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 3: if fieldTypeId == thrift.I64 { if err := p.ReadField3(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionException) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadI32(ctx); err != nil { return thrift.PrependError("error reading field 1: ", err) } else { p.Code = v } return nil } func (p *ExtensionException) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 2: ", err) } else { p.Message = v } return nil } func (p *ExtensionException) ReadField3(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadI64(ctx); err != nil { return thrift.PrependError("error reading field 3: ", err) } else { temp := ExtensionRouteUUID(v) p.UUID = temp } return nil } func (p *ExtensionException) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "ExtensionException"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField1(ctx, oprot); err != nil { return err } if err := p.writeField2(ctx, oprot); err != nil { return err } if err := p.writeField3(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionException) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "code", thrift.I32, 1); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:code: ", p), err) } if err := oprot.WriteI32(ctx, int32(p.Code)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.code (1) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 1:code: ", p), err) } return err } func (p *ExtensionException) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "message", thrift.STRING, 2); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:message: ", p), err) } if err := oprot.WriteString(ctx, string(p.Message)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.message (2) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 2:message: ", p), err) } return err } func (p *ExtensionException) writeField3(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "uuid", thrift.I64, 3); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 3:uuid: ", p), err) } if err := oprot.WriteI64(ctx, int64(p.UUID)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.uuid (3) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 3:uuid: ", p), err) } return err } func (p *ExtensionException) Equals(other *ExtensionException) bool { if p == other { return true } else if p == nil || other == nil { return false } if p.Code != other.Code { return false } if p.Message != other.Message { return false } if p.UUID != other.UUID { return false } return true } func (p *ExtensionException) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionException(%+v)", *p) } func (p *ExtensionException) Error() string { return p.String() } func (ExtensionException) TExceptionType() thrift.TExceptionType { return thrift.TExceptionTypeCompiled } var _ thrift.TException = (*ExtensionException)(nil) type Extension interface { Ping(ctx context.Context) (_r *ExtensionStatus, _err error) // Parameters: // - Registry // - Item // - Request Call(ctx context.Context, registry string, item string, request ExtensionPluginRequest) (_r *ExtensionResponse, _err error) Shutdown(ctx context.Context) (_err error) } type ExtensionClient struct { c thrift.TClient meta thrift.ResponseMeta } func NewExtensionClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *ExtensionClient { return &ExtensionClient{ c: thrift.NewTStandardClient(f.GetProtocol(t), f.GetProtocol(t)), } } func NewExtensionClientProtocol(t thrift.TTransport, iprot thrift.TProtocol, oprot thrift.TProtocol) *ExtensionClient { return &ExtensionClient{ c: thrift.NewTStandardClient(iprot, oprot), } } func NewExtensionClient(c thrift.TClient) *ExtensionClient { return &ExtensionClient{ c: c, } } func (p *ExtensionClient) Client_() thrift.TClient { return p.c } func (p *ExtensionClient) LastResponseMeta_() thrift.ResponseMeta { return p.meta } func (p *ExtensionClient) SetLastResponseMeta_(meta thrift.ResponseMeta) { p.meta = meta } func (p *ExtensionClient) Ping(ctx context.Context) (_r *ExtensionStatus, _err error) { var _args5 ExtensionPingArgs var _result7 ExtensionPingResult var _meta6 thrift.ResponseMeta _meta6, _err = p.Client_().Call(ctx, "ping", &_args5, &_result7) p.SetLastResponseMeta_(_meta6) if _err != nil { return } if _ret8 := _result7.GetSuccess(); _ret8 != nil { return _ret8, nil } return nil, thrift.NewTApplicationException(thrift.MISSING_RESULT, "ping failed: unknown result") } // Parameters: // - Registry // - Item // - Request func (p *ExtensionClient) Call(ctx context.Context, registry string, item string, request ExtensionPluginRequest) (_r *ExtensionResponse, _err error) { var _args9 ExtensionCallArgs _args9.Registry = registry _args9.Item = item _args9.Request = request var _result11 ExtensionCallResult var _meta10 thrift.ResponseMeta _meta10, _err = p.Client_().Call(ctx, "call", &_args9, &_result11) p.SetLastResponseMeta_(_meta10) if _err != nil { return } if _ret12 := _result11.GetSuccess(); _ret12 != nil { return _ret12, nil } return nil, thrift.NewTApplicationException(thrift.MISSING_RESULT, "call failed: unknown result") } func (p *ExtensionClient) Shutdown(ctx context.Context) (_err error) { var _args13 ExtensionShutdownArgs var _result15 ExtensionShutdownResult var _meta14 thrift.ResponseMeta _meta14, _err = p.Client_().Call(ctx, "shutdown", &_args13, &_result15) p.SetLastResponseMeta_(_meta14) if _err != nil { return } return nil } type ExtensionProcessor struct { processorMap map[string]thrift.TProcessorFunction handler Extension } func (p *ExtensionProcessor) AddToProcessorMap(key string, processor thrift.TProcessorFunction) { p.processorMap[key] = processor } func (p *ExtensionProcessor) GetProcessorFunction(key string) (processor thrift.TProcessorFunction, ok bool) { processor, ok = p.processorMap[key] return processor, ok } func (p *ExtensionProcessor) ProcessorMap() map[string]thrift.TProcessorFunction { return p.processorMap } func NewExtensionProcessor(handler Extension) *ExtensionProcessor { self16 := &ExtensionProcessor{handler: handler, processorMap: make(map[string]thrift.TProcessorFunction)} self16.processorMap["ping"] = &extensionProcessorPing{handler: handler} self16.processorMap["call"] = &extensionProcessorCall{handler: handler} self16.processorMap["shutdown"] = &extensionProcessorShutdown{handler: handler} return self16 } func (p *ExtensionProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { name, _, seqId, err2 := iprot.ReadMessageBegin(ctx) if err2 != nil { return false, thrift.WrapTException(err2) } if processor, ok := p.GetProcessorFunction(name); ok { return processor.Process(ctx, seqId, iprot, oprot) } iprot.Skip(ctx, thrift.STRUCT) iprot.ReadMessageEnd(ctx) x17 := thrift.NewTApplicationException(thrift.UNKNOWN_METHOD, "Unknown function "+name) oprot.WriteMessageBegin(ctx, name, thrift.EXCEPTION, seqId) x17.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return false, x17 } type extensionProcessorPing struct { handler Extension } func (p *extensionProcessorPing) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { args := ExtensionPingArgs{} var err2 error if err2 = args.Read(ctx, iprot); err2 != nil { iprot.ReadMessageEnd(ctx) x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) oprot.WriteMessageBegin(ctx, "ping", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return false, thrift.WrapTException(err2) } iprot.ReadMessageEnd(ctx) tickerCancel := func() {} // Start a goroutine to do server side connectivity check. if thrift.ServerConnectivityCheckInterval > 0 { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() var tickerCtx context.Context tickerCtx, tickerCancel = context.WithCancel(context.Background()) defer tickerCancel() go func(ctx context.Context, cancel context.CancelFunc) { ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if !iprot.Transport().IsOpen() { cancel() return } } } }(tickerCtx, cancel) } result := ExtensionPingResult{} var retval *ExtensionStatus if retval, err2 = p.handler.Ping(ctx); err2 != nil { tickerCancel() if err2 == thrift.ErrAbandonRequest { return false, thrift.WrapTException(err2) } x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing ping: "+err2.Error()) oprot.WriteMessageBegin(ctx, "ping", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return true, thrift.WrapTException(err2) } else { result.Success = retval } tickerCancel() if err2 = oprot.WriteMessageBegin(ctx, "ping", thrift.REPLY, seqId); err2 != nil { err = thrift.WrapTException(err2) } if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.Flush(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err != nil { return } return true, err } type extensionProcessorCall struct { handler Extension } func (p *extensionProcessorCall) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { args := ExtensionCallArgs{} var err2 error if err2 = args.Read(ctx, iprot); err2 != nil { iprot.ReadMessageEnd(ctx) x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) oprot.WriteMessageBegin(ctx, "call", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return false, thrift.WrapTException(err2) } iprot.ReadMessageEnd(ctx) tickerCancel := func() {} // Start a goroutine to do server side connectivity check. if thrift.ServerConnectivityCheckInterval > 0 { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() var tickerCtx context.Context tickerCtx, tickerCancel = context.WithCancel(context.Background()) defer tickerCancel() go func(ctx context.Context, cancel context.CancelFunc) { ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if !iprot.Transport().IsOpen() { cancel() return } } } }(tickerCtx, cancel) } result := ExtensionCallResult{} var retval *ExtensionResponse if retval, err2 = p.handler.Call(ctx, args.Registry, args.Item, args.Request); err2 != nil { tickerCancel() if err2 == thrift.ErrAbandonRequest { return false, thrift.WrapTException(err2) } x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing call: "+err2.Error()) oprot.WriteMessageBegin(ctx, "call", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return true, thrift.WrapTException(err2) } else { result.Success = retval } tickerCancel() if err2 = oprot.WriteMessageBegin(ctx, "call", thrift.REPLY, seqId); err2 != nil { err = thrift.WrapTException(err2) } if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.Flush(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err != nil { return } return true, err } type extensionProcessorShutdown struct { handler Extension } func (p *extensionProcessorShutdown) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { args := ExtensionShutdownArgs{} var err2 error if err2 = args.Read(ctx, iprot); err2 != nil { iprot.ReadMessageEnd(ctx) x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) oprot.WriteMessageBegin(ctx, "shutdown", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return false, thrift.WrapTException(err2) } iprot.ReadMessageEnd(ctx) tickerCancel := func() {} // Start a goroutine to do server side connectivity check. if thrift.ServerConnectivityCheckInterval > 0 { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() var tickerCtx context.Context tickerCtx, tickerCancel = context.WithCancel(context.Background()) defer tickerCancel() go func(ctx context.Context, cancel context.CancelFunc) { ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if !iprot.Transport().IsOpen() { cancel() return } } } }(tickerCtx, cancel) } result := ExtensionShutdownResult{} if err2 = p.handler.Shutdown(ctx); err2 != nil { tickerCancel() if err2 == thrift.ErrAbandonRequest { return false, thrift.WrapTException(err2) } x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing shutdown: "+err2.Error()) oprot.WriteMessageBegin(ctx, "shutdown", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return true, thrift.WrapTException(err2) } tickerCancel() if err2 = oprot.WriteMessageBegin(ctx, "shutdown", thrift.REPLY, seqId); err2 != nil { err = thrift.WrapTException(err2) } if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.Flush(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err != nil { return } return true, err } // HELPER FUNCTIONS AND STRUCTURES type ExtensionPingArgs struct { } func NewExtensionPingArgs() *ExtensionPingArgs { return &ExtensionPingArgs{} } func (p *ExtensionPingArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionPingArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "ping_args"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionPingArgs) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionPingArgs(%+v)", *p) } // Attributes: // - Success type ExtensionPingResult struct { Success *ExtensionStatus `thrift:"success,0" db:"success" json:"success,omitempty"` } func NewExtensionPingResult() *ExtensionPingResult { return &ExtensionPingResult{} } var ExtensionPingResult_Success_DEFAULT *ExtensionStatus func (p *ExtensionPingResult) GetSuccess() *ExtensionStatus { if !p.IsSetSuccess() { return ExtensionPingResult_Success_DEFAULT } return p.Success } func (p *ExtensionPingResult) IsSetSuccess() bool { return p.Success != nil } func (p *ExtensionPingResult) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 0: if fieldTypeId == thrift.STRUCT { if err := p.ReadField0(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionPingResult) ReadField0(ctx context.Context, iprot thrift.TProtocol) error { p.Success = &ExtensionStatus{} if err := p.Success.Read(ctx, iprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err) } return nil } func (p *ExtensionPingResult) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "ping_result"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField0(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionPingResult) writeField0(ctx context.Context, oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err := oprot.WriteFieldBegin(ctx, "success", thrift.STRUCT, 0); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) } if err := p.Success.Write(ctx, oprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) } } return err } func (p *ExtensionPingResult) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionPingResult(%+v)", *p) } // Attributes: // - Registry // - Item // - Request type ExtensionCallArgs struct { Registry string `thrift:"registry,1" db:"registry" json:"registry"` Item string `thrift:"item,2" db:"item" json:"item"` Request ExtensionPluginRequest `thrift:"request,3" db:"request" json:"request"` } func NewExtensionCallArgs() *ExtensionCallArgs { return &ExtensionCallArgs{} } func (p *ExtensionCallArgs) GetRegistry() string { return p.Registry } func (p *ExtensionCallArgs) GetItem() string { return p.Item } func (p *ExtensionCallArgs) GetRequest() ExtensionPluginRequest { return p.Request } func (p *ExtensionCallArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 1: if fieldTypeId == thrift.STRING { if err := p.ReadField1(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 2: if fieldTypeId == thrift.STRING { if err := p.ReadField2(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 3: if fieldTypeId == thrift.MAP { if err := p.ReadField3(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionCallArgs) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 1: ", err) } else { p.Registry = v } return nil } func (p *ExtensionCallArgs) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 2: ", err) } else { p.Item = v } return nil } func (p *ExtensionCallArgs) ReadField3(ctx context.Context, iprot thrift.TProtocol) error { _, _, size, err := iprot.ReadMapBegin(ctx) if err != nil { return thrift.PrependError("error reading map begin: ", err) } tMap := make(ExtensionPluginRequest, size) p.Request = tMap for i := 0; i < size; i++ { var _key18 string if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 0: ", err) } else { _key18 = v } var _val19 string if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 0: ", err) } else { _val19 = v } p.Request[_key18] = _val19 } if err := iprot.ReadMapEnd(ctx); err != nil { return thrift.PrependError("error reading map end: ", err) } return nil } func (p *ExtensionCallArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "call_args"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField1(ctx, oprot); err != nil { return err } if err := p.writeField2(ctx, oprot); err != nil { return err } if err := p.writeField3(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionCallArgs) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "registry", thrift.STRING, 1); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:registry: ", p), err) } if err := oprot.WriteString(ctx, string(p.Registry)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.registry (1) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 1:registry: ", p), err) } return err } func (p *ExtensionCallArgs) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "item", thrift.STRING, 2); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:item: ", p), err) } if err := oprot.WriteString(ctx, string(p.Item)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.item (2) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 2:item: ", p), err) } return err } func (p *ExtensionCallArgs) writeField3(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "request", thrift.MAP, 3); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 3:request: ", p), err) } if err := oprot.WriteMapBegin(ctx, thrift.STRING, thrift.STRING, len(p.Request)); err != nil { return thrift.PrependError("error writing map begin: ", err) } for k, v := range p.Request { if err := oprot.WriteString(ctx, string(k)); err != nil { return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) } if err := oprot.WriteString(ctx, string(v)); err != nil { return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) } } if err := oprot.WriteMapEnd(ctx); err != nil { return thrift.PrependError("error writing map end: ", err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 3:request: ", p), err) } return err } func (p *ExtensionCallArgs) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionCallArgs(%+v)", *p) } // Attributes: // - Success type ExtensionCallResult struct { Success *ExtensionResponse `thrift:"success,0" db:"success" json:"success,omitempty"` } func NewExtensionCallResult() *ExtensionCallResult { return &ExtensionCallResult{} } var ExtensionCallResult_Success_DEFAULT *ExtensionResponse func (p *ExtensionCallResult) GetSuccess() *ExtensionResponse { if !p.IsSetSuccess() { return ExtensionCallResult_Success_DEFAULT } return p.Success } func (p *ExtensionCallResult) IsSetSuccess() bool { return p.Success != nil } func (p *ExtensionCallResult) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 0: if fieldTypeId == thrift.STRUCT { if err := p.ReadField0(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionCallResult) ReadField0(ctx context.Context, iprot thrift.TProtocol) error { p.Success = &ExtensionResponse{} if err := p.Success.Read(ctx, iprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err) } return nil } func (p *ExtensionCallResult) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "call_result"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField0(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionCallResult) writeField0(ctx context.Context, oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err := oprot.WriteFieldBegin(ctx, "success", thrift.STRUCT, 0); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) } if err := p.Success.Write(ctx, oprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) } } return err } func (p *ExtensionCallResult) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionCallResult(%+v)", *p) } type ExtensionShutdownArgs struct { } func NewExtensionShutdownArgs() *ExtensionShutdownArgs { return &ExtensionShutdownArgs{} } func (p *ExtensionShutdownArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionShutdownArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "shutdown_args"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionShutdownArgs) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionShutdownArgs(%+v)", *p) } type ExtensionShutdownResult struct { } func NewExtensionShutdownResult() *ExtensionShutdownResult { return &ExtensionShutdownResult{} } func (p *ExtensionShutdownResult) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionShutdownResult) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "shutdown_result"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionShutdownResult) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionShutdownResult(%+v)", *p) } type ExtensionManager interface { Extension Extensions(ctx context.Context) (_r InternalExtensionList, _err error) Options(ctx context.Context) (_r InternalOptionList, _err error) // Parameters: // - Info // - Registry RegisterExtension(ctx context.Context, info *InternalExtensionInfo, registry ExtensionRegistry) (_r *ExtensionStatus, _err error) // Parameters: // - UUID DeregisterExtension(ctx context.Context, uuid ExtensionRouteUUID) (_r *ExtensionStatus, _err error) // Parameters: // - Sql Query(ctx context.Context, sql string) (_r *ExtensionResponse, _err error) // Parameters: // - Sql GetQueryColumns(ctx context.Context, sql string) (_r *ExtensionResponse, _err error) } type ExtensionManagerClient struct { *ExtensionClient } func NewExtensionManagerClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *ExtensionManagerClient { return &ExtensionManagerClient{ExtensionClient: NewExtensionClientFactory(t, f)} } func NewExtensionManagerClientProtocol(t thrift.TTransport, iprot thrift.TProtocol, oprot thrift.TProtocol) *ExtensionManagerClient { return &ExtensionManagerClient{ExtensionClient: NewExtensionClientProtocol(t, iprot, oprot)} } func NewExtensionManagerClient(c thrift.TClient) *ExtensionManagerClient { return &ExtensionManagerClient{ ExtensionClient: NewExtensionClient(c), } } func (p *ExtensionManagerClient) Extensions(ctx context.Context) (_r InternalExtensionList, _err error) { var _args28 ExtensionManagerExtensionsArgs var _result30 ExtensionManagerExtensionsResult var _meta29 thrift.ResponseMeta _meta29, _err = p.Client_().Call(ctx, "extensions", &_args28, &_result30) p.SetLastResponseMeta_(_meta29) if _err != nil { return } return _result30.GetSuccess(), nil } func (p *ExtensionManagerClient) Options(ctx context.Context) (_r InternalOptionList, _err error) { var _args31 ExtensionManagerOptionsArgs var _result33 ExtensionManagerOptionsResult var _meta32 thrift.ResponseMeta _meta32, _err = p.Client_().Call(ctx, "options", &_args31, &_result33) p.SetLastResponseMeta_(_meta32) if _err != nil { return } return _result33.GetSuccess(), nil } // Parameters: // - Info // - Registry func (p *ExtensionManagerClient) RegisterExtension(ctx context.Context, info *InternalExtensionInfo, registry ExtensionRegistry) (_r *ExtensionStatus, _err error) { var _args34 ExtensionManagerRegisterExtensionArgs _args34.Info = info _args34.Registry = registry var _result36 ExtensionManagerRegisterExtensionResult var _meta35 thrift.ResponseMeta _meta35, _err = p.Client_().Call(ctx, "registerExtension", &_args34, &_result36) p.SetLastResponseMeta_(_meta35) if _err != nil { return } if _ret37 := _result36.GetSuccess(); _ret37 != nil { return _ret37, nil } return nil, thrift.NewTApplicationException(thrift.MISSING_RESULT, "registerExtension failed: unknown result") } // Parameters: // - UUID func (p *ExtensionManagerClient) DeregisterExtension(ctx context.Context, uuid ExtensionRouteUUID) (_r *ExtensionStatus, _err error) { var _args38 ExtensionManagerDeregisterExtensionArgs _args38.UUID = uuid var _result40 ExtensionManagerDeregisterExtensionResult var _meta39 thrift.ResponseMeta _meta39, _err = p.Client_().Call(ctx, "deregisterExtension", &_args38, &_result40) p.SetLastResponseMeta_(_meta39) if _err != nil { return } if _ret41 := _result40.GetSuccess(); _ret41 != nil { return _ret41, nil } return nil, thrift.NewTApplicationException(thrift.MISSING_RESULT, "deregisterExtension failed: unknown result") } // Parameters: // - Sql func (p *ExtensionManagerClient) Query(ctx context.Context, sql string) (_r *ExtensionResponse, _err error) { var _args42 ExtensionManagerQueryArgs _args42.Sql = sql var _result44 ExtensionManagerQueryResult var _meta43 thrift.ResponseMeta _meta43, _err = p.Client_().Call(ctx, "query", &_args42, &_result44) p.SetLastResponseMeta_(_meta43) if _err != nil { return } if _ret45 := _result44.GetSuccess(); _ret45 != nil { return _ret45, nil } return nil, thrift.NewTApplicationException(thrift.MISSING_RESULT, "query failed: unknown result") } // Parameters: // - Sql func (p *ExtensionManagerClient) GetQueryColumns(ctx context.Context, sql string) (_r *ExtensionResponse, _err error) { var _args46 ExtensionManagerGetQueryColumnsArgs _args46.Sql = sql var _result48 ExtensionManagerGetQueryColumnsResult var _meta47 thrift.ResponseMeta _meta47, _err = p.Client_().Call(ctx, "getQueryColumns", &_args46, &_result48) p.SetLastResponseMeta_(_meta47) if _err != nil { return } if _ret49 := _result48.GetSuccess(); _ret49 != nil { return _ret49, nil } return nil, thrift.NewTApplicationException(thrift.MISSING_RESULT, "getQueryColumns failed: unknown result") } type ExtensionManagerProcessor struct { *ExtensionProcessor } func NewExtensionManagerProcessor(handler ExtensionManager) *ExtensionManagerProcessor { self50 := &ExtensionManagerProcessor{NewExtensionProcessor(handler)} self50.AddToProcessorMap("extensions", &extensionManagerProcessorExtensions{handler: handler}) self50.AddToProcessorMap("options", &extensionManagerProcessorOptions{handler: handler}) self50.AddToProcessorMap("registerExtension", &extensionManagerProcessorRegisterExtension{handler: handler}) self50.AddToProcessorMap("deregisterExtension", &extensionManagerProcessorDeregisterExtension{handler: handler}) self50.AddToProcessorMap("query", &extensionManagerProcessorQuery{handler: handler}) self50.AddToProcessorMap("getQueryColumns", &extensionManagerProcessorGetQueryColumns{handler: handler}) return self50 } type extensionManagerProcessorExtensions struct { handler ExtensionManager } func (p *extensionManagerProcessorExtensions) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { args := ExtensionManagerExtensionsArgs{} var err2 error if err2 = args.Read(ctx, iprot); err2 != nil { iprot.ReadMessageEnd(ctx) x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) oprot.WriteMessageBegin(ctx, "extensions", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return false, thrift.WrapTException(err2) } iprot.ReadMessageEnd(ctx) tickerCancel := func() {} // Start a goroutine to do server side connectivity check. if thrift.ServerConnectivityCheckInterval > 0 { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() var tickerCtx context.Context tickerCtx, tickerCancel = context.WithCancel(context.Background()) defer tickerCancel() go func(ctx context.Context, cancel context.CancelFunc) { ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if !iprot.Transport().IsOpen() { cancel() return } } } }(tickerCtx, cancel) } result := ExtensionManagerExtensionsResult{} var retval InternalExtensionList if retval, err2 = p.handler.Extensions(ctx); err2 != nil { tickerCancel() if err2 == thrift.ErrAbandonRequest { return false, thrift.WrapTException(err2) } x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing extensions: "+err2.Error()) oprot.WriteMessageBegin(ctx, "extensions", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return true, thrift.WrapTException(err2) } else { result.Success = retval } tickerCancel() if err2 = oprot.WriteMessageBegin(ctx, "extensions", thrift.REPLY, seqId); err2 != nil { err = thrift.WrapTException(err2) } if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.Flush(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err != nil { return } return true, err } type extensionManagerProcessorOptions struct { handler ExtensionManager } func (p *extensionManagerProcessorOptions) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { args := ExtensionManagerOptionsArgs{} var err2 error if err2 = args.Read(ctx, iprot); err2 != nil { iprot.ReadMessageEnd(ctx) x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) oprot.WriteMessageBegin(ctx, "options", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return false, thrift.WrapTException(err2) } iprot.ReadMessageEnd(ctx) tickerCancel := func() {} // Start a goroutine to do server side connectivity check. if thrift.ServerConnectivityCheckInterval > 0 { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() var tickerCtx context.Context tickerCtx, tickerCancel = context.WithCancel(context.Background()) defer tickerCancel() go func(ctx context.Context, cancel context.CancelFunc) { ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if !iprot.Transport().IsOpen() { cancel() return } } } }(tickerCtx, cancel) } result := ExtensionManagerOptionsResult{} var retval InternalOptionList if retval, err2 = p.handler.Options(ctx); err2 != nil { tickerCancel() if err2 == thrift.ErrAbandonRequest { return false, thrift.WrapTException(err2) } x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing options: "+err2.Error()) oprot.WriteMessageBegin(ctx, "options", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return true, thrift.WrapTException(err2) } else { result.Success = retval } tickerCancel() if err2 = oprot.WriteMessageBegin(ctx, "options", thrift.REPLY, seqId); err2 != nil { err = thrift.WrapTException(err2) } if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.Flush(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err != nil { return } return true, err } type extensionManagerProcessorRegisterExtension struct { handler ExtensionManager } func (p *extensionManagerProcessorRegisterExtension) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { args := ExtensionManagerRegisterExtensionArgs{} var err2 error if err2 = args.Read(ctx, iprot); err2 != nil { iprot.ReadMessageEnd(ctx) x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) oprot.WriteMessageBegin(ctx, "registerExtension", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return false, thrift.WrapTException(err2) } iprot.ReadMessageEnd(ctx) tickerCancel := func() {} // Start a goroutine to do server side connectivity check. if thrift.ServerConnectivityCheckInterval > 0 { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() var tickerCtx context.Context tickerCtx, tickerCancel = context.WithCancel(context.Background()) defer tickerCancel() go func(ctx context.Context, cancel context.CancelFunc) { ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if !iprot.Transport().IsOpen() { cancel() return } } } }(tickerCtx, cancel) } result := ExtensionManagerRegisterExtensionResult{} var retval *ExtensionStatus if retval, err2 = p.handler.RegisterExtension(ctx, args.Info, args.Registry); err2 != nil { tickerCancel() if err2 == thrift.ErrAbandonRequest { return false, thrift.WrapTException(err2) } x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing registerExtension: "+err2.Error()) oprot.WriteMessageBegin(ctx, "registerExtension", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return true, thrift.WrapTException(err2) } else { result.Success = retval } tickerCancel() if err2 = oprot.WriteMessageBegin(ctx, "registerExtension", thrift.REPLY, seqId); err2 != nil { err = thrift.WrapTException(err2) } if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.Flush(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err != nil { return } return true, err } type extensionManagerProcessorDeregisterExtension struct { handler ExtensionManager } func (p *extensionManagerProcessorDeregisterExtension) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { args := ExtensionManagerDeregisterExtensionArgs{} var err2 error if err2 = args.Read(ctx, iprot); err2 != nil { iprot.ReadMessageEnd(ctx) x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) oprot.WriteMessageBegin(ctx, "deregisterExtension", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return false, thrift.WrapTException(err2) } iprot.ReadMessageEnd(ctx) tickerCancel := func() {} // Start a goroutine to do server side connectivity check. if thrift.ServerConnectivityCheckInterval > 0 { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() var tickerCtx context.Context tickerCtx, tickerCancel = context.WithCancel(context.Background()) defer tickerCancel() go func(ctx context.Context, cancel context.CancelFunc) { ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if !iprot.Transport().IsOpen() { cancel() return } } } }(tickerCtx, cancel) } result := ExtensionManagerDeregisterExtensionResult{} var retval *ExtensionStatus if retval, err2 = p.handler.DeregisterExtension(ctx, args.UUID); err2 != nil { tickerCancel() if err2 == thrift.ErrAbandonRequest { return false, thrift.WrapTException(err2) } x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing deregisterExtension: "+err2.Error()) oprot.WriteMessageBegin(ctx, "deregisterExtension", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return true, thrift.WrapTException(err2) } else { result.Success = retval } tickerCancel() if err2 = oprot.WriteMessageBegin(ctx, "deregisterExtension", thrift.REPLY, seqId); err2 != nil { err = thrift.WrapTException(err2) } if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.Flush(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err != nil { return } return true, err } type extensionManagerProcessorQuery struct { handler ExtensionManager } func (p *extensionManagerProcessorQuery) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { args := ExtensionManagerQueryArgs{} var err2 error if err2 = args.Read(ctx, iprot); err2 != nil { iprot.ReadMessageEnd(ctx) x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) oprot.WriteMessageBegin(ctx, "query", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return false, thrift.WrapTException(err2) } iprot.ReadMessageEnd(ctx) tickerCancel := func() {} // Start a goroutine to do server side connectivity check. if thrift.ServerConnectivityCheckInterval > 0 { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() var tickerCtx context.Context tickerCtx, tickerCancel = context.WithCancel(context.Background()) defer tickerCancel() go func(ctx context.Context, cancel context.CancelFunc) { ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if !iprot.Transport().IsOpen() { cancel() return } } } }(tickerCtx, cancel) } result := ExtensionManagerQueryResult{} var retval *ExtensionResponse if retval, err2 = p.handler.Query(ctx, args.Sql); err2 != nil { tickerCancel() if err2 == thrift.ErrAbandonRequest { return false, thrift.WrapTException(err2) } x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing query: "+err2.Error()) oprot.WriteMessageBegin(ctx, "query", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return true, thrift.WrapTException(err2) } else { result.Success = retval } tickerCancel() if err2 = oprot.WriteMessageBegin(ctx, "query", thrift.REPLY, seqId); err2 != nil { err = thrift.WrapTException(err2) } if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.Flush(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err != nil { return } return true, err } type extensionManagerProcessorGetQueryColumns struct { handler ExtensionManager } func (p *extensionManagerProcessorGetQueryColumns) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { args := ExtensionManagerGetQueryColumnsArgs{} var err2 error if err2 = args.Read(ctx, iprot); err2 != nil { iprot.ReadMessageEnd(ctx) x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) oprot.WriteMessageBegin(ctx, "getQueryColumns", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return false, thrift.WrapTException(err2) } iprot.ReadMessageEnd(ctx) tickerCancel := func() {} // Start a goroutine to do server side connectivity check. if thrift.ServerConnectivityCheckInterval > 0 { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() var tickerCtx context.Context tickerCtx, tickerCancel = context.WithCancel(context.Background()) defer tickerCancel() go func(ctx context.Context, cancel context.CancelFunc) { ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if !iprot.Transport().IsOpen() { cancel() return } } } }(tickerCtx, cancel) } result := ExtensionManagerGetQueryColumnsResult{} var retval *ExtensionResponse if retval, err2 = p.handler.GetQueryColumns(ctx, args.Sql); err2 != nil { tickerCancel() if err2 == thrift.ErrAbandonRequest { return false, thrift.WrapTException(err2) } x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing getQueryColumns: "+err2.Error()) oprot.WriteMessageBegin(ctx, "getQueryColumns", thrift.EXCEPTION, seqId) x.Write(ctx, oprot) oprot.WriteMessageEnd(ctx) oprot.Flush(ctx) return true, thrift.WrapTException(err2) } else { result.Success = retval } tickerCancel() if err2 = oprot.WriteMessageBegin(ctx, "getQueryColumns", thrift.REPLY, seqId); err2 != nil { err = thrift.WrapTException(err2) } if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err2 = oprot.Flush(ctx); err == nil && err2 != nil { err = thrift.WrapTException(err2) } if err != nil { return } return true, err } // HELPER FUNCTIONS AND STRUCTURES type ExtensionManagerExtensionsArgs struct { } func NewExtensionManagerExtensionsArgs() *ExtensionManagerExtensionsArgs { return &ExtensionManagerExtensionsArgs{} } func (p *ExtensionManagerExtensionsArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerExtensionsArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "extensions_args"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerExtensionsArgs) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerExtensionsArgs(%+v)", *p) } // Attributes: // - Success type ExtensionManagerExtensionsResult struct { Success InternalExtensionList `thrift:"success,0" db:"success" json:"success,omitempty"` } func NewExtensionManagerExtensionsResult() *ExtensionManagerExtensionsResult { return &ExtensionManagerExtensionsResult{} } var ExtensionManagerExtensionsResult_Success_DEFAULT InternalExtensionList func (p *ExtensionManagerExtensionsResult) GetSuccess() InternalExtensionList { return p.Success } func (p *ExtensionManagerExtensionsResult) IsSetSuccess() bool { return p.Success != nil } func (p *ExtensionManagerExtensionsResult) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 0: if fieldTypeId == thrift.MAP { if err := p.ReadField0(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerExtensionsResult) ReadField0(ctx context.Context, iprot thrift.TProtocol) error { _, _, size, err := iprot.ReadMapBegin(ctx) if err != nil { return thrift.PrependError("error reading map begin: ", err) } tMap := make(InternalExtensionList, size) p.Success = tMap for i := 0; i < size; i++ { var _key51 ExtensionRouteUUID if v, err := iprot.ReadI64(ctx); err != nil { return thrift.PrependError("error reading field 0: ", err) } else { temp := ExtensionRouteUUID(v) _key51 = temp } _val52 := &InternalExtensionInfo{} if err := _val52.Read(ctx, iprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", _val52), err) } p.Success[_key51] = _val52 } if err := iprot.ReadMapEnd(ctx); err != nil { return thrift.PrependError("error reading map end: ", err) } return nil } func (p *ExtensionManagerExtensionsResult) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "extensions_result"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField0(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerExtensionsResult) writeField0(ctx context.Context, oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err := oprot.WriteFieldBegin(ctx, "success", thrift.MAP, 0); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) } if err := oprot.WriteMapBegin(ctx, thrift.I64, thrift.STRUCT, len(p.Success)); err != nil { return thrift.PrependError("error writing map begin: ", err) } for k, v := range p.Success { if err := oprot.WriteI64(ctx, int64(k)); err != nil { return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) } if err := v.Write(ctx, oprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", v), err) } } if err := oprot.WriteMapEnd(ctx); err != nil { return thrift.PrependError("error writing map end: ", err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) } } return err } func (p *ExtensionManagerExtensionsResult) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerExtensionsResult(%+v)", *p) } type ExtensionManagerOptionsArgs struct { } func NewExtensionManagerOptionsArgs() *ExtensionManagerOptionsArgs { return &ExtensionManagerOptionsArgs{} } func (p *ExtensionManagerOptionsArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerOptionsArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "options_args"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerOptionsArgs) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerOptionsArgs(%+v)", *p) } // Attributes: // - Success type ExtensionManagerOptionsResult struct { Success InternalOptionList `thrift:"success,0" db:"success" json:"success,omitempty"` } func NewExtensionManagerOptionsResult() *ExtensionManagerOptionsResult { return &ExtensionManagerOptionsResult{} } var ExtensionManagerOptionsResult_Success_DEFAULT InternalOptionList func (p *ExtensionManagerOptionsResult) GetSuccess() InternalOptionList { return p.Success } func (p *ExtensionManagerOptionsResult) IsSetSuccess() bool { return p.Success != nil } func (p *ExtensionManagerOptionsResult) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 0: if fieldTypeId == thrift.MAP { if err := p.ReadField0(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerOptionsResult) ReadField0(ctx context.Context, iprot thrift.TProtocol) error { _, _, size, err := iprot.ReadMapBegin(ctx) if err != nil { return thrift.PrependError("error reading map begin: ", err) } tMap := make(InternalOptionList, size) p.Success = tMap for i := 0; i < size; i++ { var _key53 string if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 0: ", err) } else { _key53 = v } _val54 := &InternalOptionInfo{} if err := _val54.Read(ctx, iprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", _val54), err) } p.Success[_key53] = _val54 } if err := iprot.ReadMapEnd(ctx); err != nil { return thrift.PrependError("error reading map end: ", err) } return nil } func (p *ExtensionManagerOptionsResult) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "options_result"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField0(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerOptionsResult) writeField0(ctx context.Context, oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err := oprot.WriteFieldBegin(ctx, "success", thrift.MAP, 0); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) } if err := oprot.WriteMapBegin(ctx, thrift.STRING, thrift.STRUCT, len(p.Success)); err != nil { return thrift.PrependError("error writing map begin: ", err) } for k, v := range p.Success { if err := oprot.WriteString(ctx, string(k)); err != nil { return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) } if err := v.Write(ctx, oprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", v), err) } } if err := oprot.WriteMapEnd(ctx); err != nil { return thrift.PrependError("error writing map end: ", err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) } } return err } func (p *ExtensionManagerOptionsResult) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerOptionsResult(%+v)", *p) } // Attributes: // - Info // - Registry type ExtensionManagerRegisterExtensionArgs struct { Info *InternalExtensionInfo `thrift:"info,1" db:"info" json:"info"` Registry ExtensionRegistry `thrift:"registry,2" db:"registry" json:"registry"` } func NewExtensionManagerRegisterExtensionArgs() *ExtensionManagerRegisterExtensionArgs { return &ExtensionManagerRegisterExtensionArgs{} } var ExtensionManagerRegisterExtensionArgs_Info_DEFAULT *InternalExtensionInfo func (p *ExtensionManagerRegisterExtensionArgs) GetInfo() *InternalExtensionInfo { if !p.IsSetInfo() { return ExtensionManagerRegisterExtensionArgs_Info_DEFAULT } return p.Info } func (p *ExtensionManagerRegisterExtensionArgs) GetRegistry() ExtensionRegistry { return p.Registry } func (p *ExtensionManagerRegisterExtensionArgs) IsSetInfo() bool { return p.Info != nil } func (p *ExtensionManagerRegisterExtensionArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 1: if fieldTypeId == thrift.STRUCT { if err := p.ReadField1(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } case 2: if fieldTypeId == thrift.MAP { if err := p.ReadField2(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerRegisterExtensionArgs) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { p.Info = &InternalExtensionInfo{} if err := p.Info.Read(ctx, iprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Info), err) } return nil } func (p *ExtensionManagerRegisterExtensionArgs) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { _, _, size, err := iprot.ReadMapBegin(ctx) if err != nil { return thrift.PrependError("error reading map begin: ", err) } tMap := make(ExtensionRegistry, size) p.Registry = tMap for i := 0; i < size; i++ { var _key55 string if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 0: ", err) } else { _key55 = v } _, _, size, err := iprot.ReadMapBegin(ctx) if err != nil { return thrift.PrependError("error reading map begin: ", err) } tMap := make(ExtensionRouteTable, size) _val56 := tMap for i := 0; i < size; i++ { var _key57 string if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 0: ", err) } else { _key57 = v } _, size, err := iprot.ReadListBegin(ctx) if err != nil { return thrift.PrependError("error reading list begin: ", err) } tSlice := make(ExtensionPluginResponse, 0, size) _val58 := tSlice for i := 0; i < size; i++ { _, _, size, err := iprot.ReadMapBegin(ctx) if err != nil { return thrift.PrependError("error reading map begin: ", err) } tMap := make(map[string]string, size) _elem59 := tMap for i := 0; i < size; i++ { var _key60 string if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 0: ", err) } else { _key60 = v } var _val61 string if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 0: ", err) } else { _val61 = v } _elem59[_key60] = _val61 } if err := iprot.ReadMapEnd(ctx); err != nil { return thrift.PrependError("error reading map end: ", err) } _val58 = append(_val58, _elem59) } if err := iprot.ReadListEnd(ctx); err != nil { return thrift.PrependError("error reading list end: ", err) } _val56[_key57] = _val58 } if err := iprot.ReadMapEnd(ctx); err != nil { return thrift.PrependError("error reading map end: ", err) } p.Registry[_key55] = _val56 } if err := iprot.ReadMapEnd(ctx); err != nil { return thrift.PrependError("error reading map end: ", err) } return nil } func (p *ExtensionManagerRegisterExtensionArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "registerExtension_args"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField1(ctx, oprot); err != nil { return err } if err := p.writeField2(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerRegisterExtensionArgs) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "info", thrift.STRUCT, 1); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:info: ", p), err) } if err := p.Info.Write(ctx, oprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Info), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 1:info: ", p), err) } return err } func (p *ExtensionManagerRegisterExtensionArgs) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "registry", thrift.MAP, 2); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:registry: ", p), err) } if err := oprot.WriteMapBegin(ctx, thrift.STRING, thrift.MAP, len(p.Registry)); err != nil { return thrift.PrependError("error writing map begin: ", err) } for k, v := range p.Registry { if err := oprot.WriteString(ctx, string(k)); err != nil { return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) } if err := oprot.WriteMapBegin(ctx, thrift.STRING, thrift.LIST, len(v)); err != nil { return thrift.PrependError("error writing map begin: ", err) } for k, v := range v { if err := oprot.WriteString(ctx, string(k)); err != nil { return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) } if err := oprot.WriteListBegin(ctx, thrift.MAP, len(v)); err != nil { return thrift.PrependError("error writing list begin: ", err) } for _, v := range v { if err := oprot.WriteMapBegin(ctx, thrift.STRING, thrift.STRING, len(v)); err != nil { return thrift.PrependError("error writing map begin: ", err) } for k, v := range v { if err := oprot.WriteString(ctx, string(k)); err != nil { return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) } if err := oprot.WriteString(ctx, string(v)); err != nil { return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) } } if err := oprot.WriteMapEnd(ctx); err != nil { return thrift.PrependError("error writing map end: ", err) } } if err := oprot.WriteListEnd(ctx); err != nil { return thrift.PrependError("error writing list end: ", err) } } if err := oprot.WriteMapEnd(ctx); err != nil { return thrift.PrependError("error writing map end: ", err) } } if err := oprot.WriteMapEnd(ctx); err != nil { return thrift.PrependError("error writing map end: ", err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 2:registry: ", p), err) } return err } func (p *ExtensionManagerRegisterExtensionArgs) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerRegisterExtensionArgs(%+v)", *p) } // Attributes: // - Success type ExtensionManagerRegisterExtensionResult struct { Success *ExtensionStatus `thrift:"success,0" db:"success" json:"success,omitempty"` } func NewExtensionManagerRegisterExtensionResult() *ExtensionManagerRegisterExtensionResult { return &ExtensionManagerRegisterExtensionResult{} } var ExtensionManagerRegisterExtensionResult_Success_DEFAULT *ExtensionStatus func (p *ExtensionManagerRegisterExtensionResult) GetSuccess() *ExtensionStatus { if !p.IsSetSuccess() { return ExtensionManagerRegisterExtensionResult_Success_DEFAULT } return p.Success } func (p *ExtensionManagerRegisterExtensionResult) IsSetSuccess() bool { return p.Success != nil } func (p *ExtensionManagerRegisterExtensionResult) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 0: if fieldTypeId == thrift.STRUCT { if err := p.ReadField0(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerRegisterExtensionResult) ReadField0(ctx context.Context, iprot thrift.TProtocol) error { p.Success = &ExtensionStatus{} if err := p.Success.Read(ctx, iprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err) } return nil } func (p *ExtensionManagerRegisterExtensionResult) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "registerExtension_result"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField0(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerRegisterExtensionResult) writeField0(ctx context.Context, oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err := oprot.WriteFieldBegin(ctx, "success", thrift.STRUCT, 0); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) } if err := p.Success.Write(ctx, oprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) } } return err } func (p *ExtensionManagerRegisterExtensionResult) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerRegisterExtensionResult(%+v)", *p) } // Attributes: // - UUID type ExtensionManagerDeregisterExtensionArgs struct { UUID ExtensionRouteUUID `thrift:"uuid,1" db:"uuid" json:"uuid"` } func NewExtensionManagerDeregisterExtensionArgs() *ExtensionManagerDeregisterExtensionArgs { return &ExtensionManagerDeregisterExtensionArgs{} } func (p *ExtensionManagerDeregisterExtensionArgs) GetUUID() ExtensionRouteUUID { return p.UUID } func (p *ExtensionManagerDeregisterExtensionArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 1: if fieldTypeId == thrift.I64 { if err := p.ReadField1(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerDeregisterExtensionArgs) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadI64(ctx); err != nil { return thrift.PrependError("error reading field 1: ", err) } else { temp := ExtensionRouteUUID(v) p.UUID = temp } return nil } func (p *ExtensionManagerDeregisterExtensionArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "deregisterExtension_args"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField1(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerDeregisterExtensionArgs) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "uuid", thrift.I64, 1); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:uuid: ", p), err) } if err := oprot.WriteI64(ctx, int64(p.UUID)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.uuid (1) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 1:uuid: ", p), err) } return err } func (p *ExtensionManagerDeregisterExtensionArgs) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerDeregisterExtensionArgs(%+v)", *p) } // Attributes: // - Success type ExtensionManagerDeregisterExtensionResult struct { Success *ExtensionStatus `thrift:"success,0" db:"success" json:"success,omitempty"` } func NewExtensionManagerDeregisterExtensionResult() *ExtensionManagerDeregisterExtensionResult { return &ExtensionManagerDeregisterExtensionResult{} } var ExtensionManagerDeregisterExtensionResult_Success_DEFAULT *ExtensionStatus func (p *ExtensionManagerDeregisterExtensionResult) GetSuccess() *ExtensionStatus { if !p.IsSetSuccess() { return ExtensionManagerDeregisterExtensionResult_Success_DEFAULT } return p.Success } func (p *ExtensionManagerDeregisterExtensionResult) IsSetSuccess() bool { return p.Success != nil } func (p *ExtensionManagerDeregisterExtensionResult) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 0: if fieldTypeId == thrift.STRUCT { if err := p.ReadField0(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerDeregisterExtensionResult) ReadField0(ctx context.Context, iprot thrift.TProtocol) error { p.Success = &ExtensionStatus{} if err := p.Success.Read(ctx, iprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err) } return nil } func (p *ExtensionManagerDeregisterExtensionResult) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "deregisterExtension_result"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField0(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerDeregisterExtensionResult) writeField0(ctx context.Context, oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err := oprot.WriteFieldBegin(ctx, "success", thrift.STRUCT, 0); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) } if err := p.Success.Write(ctx, oprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) } } return err } func (p *ExtensionManagerDeregisterExtensionResult) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerDeregisterExtensionResult(%+v)", *p) } // Attributes: // - Sql type ExtensionManagerQueryArgs struct { Sql string `thrift:"sql,1" db:"sql" json:"sql"` } func NewExtensionManagerQueryArgs() *ExtensionManagerQueryArgs { return &ExtensionManagerQueryArgs{} } func (p *ExtensionManagerQueryArgs) GetSql() string { return p.Sql } func (p *ExtensionManagerQueryArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 1: if fieldTypeId == thrift.STRING { if err := p.ReadField1(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerQueryArgs) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 1: ", err) } else { p.Sql = v } return nil } func (p *ExtensionManagerQueryArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "query_args"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField1(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerQueryArgs) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "sql", thrift.STRING, 1); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:sql: ", p), err) } if err := oprot.WriteString(ctx, string(p.Sql)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.sql (1) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 1:sql: ", p), err) } return err } func (p *ExtensionManagerQueryArgs) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerQueryArgs(%+v)", *p) } // Attributes: // - Success type ExtensionManagerQueryResult struct { Success *ExtensionResponse `thrift:"success,0" db:"success" json:"success,omitempty"` } func NewExtensionManagerQueryResult() *ExtensionManagerQueryResult { return &ExtensionManagerQueryResult{} } var ExtensionManagerQueryResult_Success_DEFAULT *ExtensionResponse func (p *ExtensionManagerQueryResult) GetSuccess() *ExtensionResponse { if !p.IsSetSuccess() { return ExtensionManagerQueryResult_Success_DEFAULT } return p.Success } func (p *ExtensionManagerQueryResult) IsSetSuccess() bool { return p.Success != nil } func (p *ExtensionManagerQueryResult) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 0: if fieldTypeId == thrift.STRUCT { if err := p.ReadField0(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerQueryResult) ReadField0(ctx context.Context, iprot thrift.TProtocol) error { p.Success = &ExtensionResponse{} if err := p.Success.Read(ctx, iprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err) } return nil } func (p *ExtensionManagerQueryResult) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "query_result"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField0(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerQueryResult) writeField0(ctx context.Context, oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err := oprot.WriteFieldBegin(ctx, "success", thrift.STRUCT, 0); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) } if err := p.Success.Write(ctx, oprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) } } return err } func (p *ExtensionManagerQueryResult) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerQueryResult(%+v)", *p) } // Attributes: // - Sql type ExtensionManagerGetQueryColumnsArgs struct { Sql string `thrift:"sql,1" db:"sql" json:"sql"` } func NewExtensionManagerGetQueryColumnsArgs() *ExtensionManagerGetQueryColumnsArgs { return &ExtensionManagerGetQueryColumnsArgs{} } func (p *ExtensionManagerGetQueryColumnsArgs) GetSql() string { return p.Sql } func (p *ExtensionManagerGetQueryColumnsArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 1: if fieldTypeId == thrift.STRING { if err := p.ReadField1(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerGetQueryColumnsArgs) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { if v, err := iprot.ReadString(ctx); err != nil { return thrift.PrependError("error reading field 1: ", err) } else { p.Sql = v } return nil } func (p *ExtensionManagerGetQueryColumnsArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "getQueryColumns_args"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField1(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerGetQueryColumnsArgs) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { if err := oprot.WriteFieldBegin(ctx, "sql", thrift.STRING, 1); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:sql: ", p), err) } if err := oprot.WriteString(ctx, string(p.Sql)); err != nil { return thrift.PrependError(fmt.Sprintf("%T.sql (1) field write error: ", p), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 1:sql: ", p), err) } return err } func (p *ExtensionManagerGetQueryColumnsArgs) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerGetQueryColumnsArgs(%+v)", *p) } // Attributes: // - Success type ExtensionManagerGetQueryColumnsResult struct { Success *ExtensionResponse `thrift:"success,0" db:"success" json:"success,omitempty"` } func NewExtensionManagerGetQueryColumnsResult() *ExtensionManagerGetQueryColumnsResult { return &ExtensionManagerGetQueryColumnsResult{} } var ExtensionManagerGetQueryColumnsResult_Success_DEFAULT *ExtensionResponse func (p *ExtensionManagerGetQueryColumnsResult) GetSuccess() *ExtensionResponse { if !p.IsSetSuccess() { return ExtensionManagerGetQueryColumnsResult_Success_DEFAULT } return p.Success } func (p *ExtensionManagerGetQueryColumnsResult) IsSetSuccess() bool { return p.Success != nil } func (p *ExtensionManagerGetQueryColumnsResult) Read(ctx context.Context, iprot thrift.TProtocol) error { if _, err := iprot.ReadStructBegin(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) } for { _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) if err != nil { return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) } if fieldTypeId == thrift.STOP { break } switch fieldId { case 0: if fieldTypeId == thrift.STRUCT { if err := p.ReadField0(ctx, iprot); err != nil { return err } } else { if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } default: if err := iprot.Skip(ctx, fieldTypeId); err != nil { return err } } if err := iprot.ReadFieldEnd(ctx); err != nil { return err } } if err := iprot.ReadStructEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } return nil } func (p *ExtensionManagerGetQueryColumnsResult) ReadField0(ctx context.Context, iprot thrift.TProtocol) error { p.Success = &ExtensionResponse{} if err := p.Success.Read(ctx, iprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err) } return nil } func (p *ExtensionManagerGetQueryColumnsResult) Write(ctx context.Context, oprot thrift.TProtocol) error { if err := oprot.WriteStructBegin(ctx, "getQueryColumns_result"); err != nil { return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) } if p != nil { if err := p.writeField0(ctx, oprot); err != nil { return err } } if err := oprot.WriteFieldStop(ctx); err != nil { return thrift.PrependError("write field stop error: ", err) } if err := oprot.WriteStructEnd(ctx); err != nil { return thrift.PrependError("write struct stop error: ", err) } return nil } func (p *ExtensionManagerGetQueryColumnsResult) writeField0(ctx context.Context, oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err := oprot.WriteFieldBegin(ctx, "success", thrift.STRUCT, 0); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) } if err := p.Success.Write(ctx, oprot); err != nil { return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err) } if err := oprot.WriteFieldEnd(ctx); err != nil { return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) } } return err } func (p *ExtensionManagerGetQueryColumnsResult) String() string { if p == nil { return "" } return fmt.Sprintf("ExtensionManagerGetQueryColumnsResult(%+v)", *p) } ================================================ FILE: go.mod ================================================ module github.com/osquery/osquery-go require ( github.com/Microsoft/go-winio v0.6.2 github.com/apache/thrift v0.20.0 github.com/pkg/errors v0.8.0 github.com/stretchr/testify v1.8.3 go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/trace v1.16.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.16.0 // indirect golang.org/x/sys v0.25.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go 1.26 ================================================ FILE: go.sum ================================================ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/apache/thrift v0.20.0 h1:631+KvYbsBZxmuJjYwhezVsrfc/TbqtZV4QcxOX1fOI= github.com/apache/thrift v0.20.0/go.mod h1:hOk1BQqcp2OLzGsyVXdfMk7YFlMxK3aoEVhjD06QhB8= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: locker.go ================================================ package osquery import ( "context" "fmt" "time" ) // locker uses go channels to create a lock mechanism. We use channels, and not the more common mutexes, because the // latter cannot be interrupted. This allows callers to timeout without blocking on the mutex. // // We need _some_ lock mechanism because the underlying thrift socket only allows a single actor at a time. If two // goroutines are trying to use the socket at the same time, they will get protocol errors. type locker struct { c chan struct{} defaultTimeout time.Duration // Default wait time is used if context does not have a deadline maxWait time.Duration // Maximum time something is allowed to wait } func NewLocker(defaultTimeout time.Duration, maxWait time.Duration) *locker { return &locker{ c: make(chan struct{}, 1), defaultTimeout: defaultTimeout, maxWait: maxWait, } } // Lock attempts to lock l. It will wait for the shorter of (ctx deadline | defaultTimeout) and maxWait. func (l *locker) Lock(ctx context.Context) error { // Assume most callers have set a deadline on the context, and start this as being the max allowed wait time wait := l.maxWait timeoutError := "timeout after maximum of %s" // If the caller has not set a deadline, use the default. if _, ok := ctx.Deadline(); !ok { wait = l.defaultTimeout timeoutError = "timeout after %s" } timeout := time.NewTimer(wait) defer timeout.Stop() // Block until we get the lock, the context is canceled, or we time out. select { case l.c <- struct{}{}: // lock acquired return nil case <-ctx.Done(): // context has been canceled return fmt.Errorf("context canceled: %w", ctx.Err()) case <-timeout.C: // timed out return fmt.Errorf(timeoutError, wait) } } // Unlock unlocks l. It is a runtime error to unlock an unlocked locker. func (l *locker) Unlock() { select { case <-l.c: return default: // Calling Unlock on an unlocked mutex is a fatal error. We mirror that behavior here. panic("unlock of unlocked locker") } } ================================================ FILE: locker_test.go ================================================ package osquery import ( "context" "math/rand" "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestLocker(t *testing.T) { t.Parallel() tests := []struct { name string sleepTime time.Duration ctxTimeout time.Duration parallelism int expectedSuccessesRange [2]int expectedErrorRange [2]int expectedErrors []string }{ { name: "basic", sleepTime: 1 * time.Millisecond, ctxTimeout: 10 * time.Millisecond, parallelism: 5, expectedSuccessesRange: [2]int{5, 5}, }, { name: "some finishers", sleepTime: 4 * time.Millisecond, ctxTimeout: 10 * time.Millisecond, parallelism: 5, expectedSuccessesRange: [2]int{2, 3}, expectedErrorRange: [2]int{2, 3}, }, { name: "sleep longer than context", sleepTime: 150 * time.Millisecond, ctxTimeout: 10 * time.Millisecond, parallelism: 5, expectedSuccessesRange: [2]int{1, 1}, expectedErrorRange: [2]int{4, 4}, expectedErrors: []string{"context canceled: context deadline exceeded"}, }, { name: "no ctx fall back to default timeout", sleepTime: 150 * time.Millisecond, parallelism: 5, expectedSuccessesRange: [2]int{1, 1}, expectedErrorRange: [2]int{4, 4}, expectedErrors: []string{"timeout after 100ms"}, }, { name: "ctx longer than maxwait", sleepTime: 250 * time.Millisecond, ctxTimeout: 10 * time.Second, parallelism: 5, expectedSuccessesRange: [2]int{1, 1}, expectedErrorRange: [2]int{4, 4}, expectedErrors: []string{"timeout after maximum of 200ms"}, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() doer := NewThingDoer() wait := sync.WaitGroup{} for i := 0; i < tt.parallelism; i++ { wait.Add(1) go func() { defer wait.Done() ctx := context.TODO() if tt.ctxTimeout != 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, tt.ctxTimeout) defer cancel() } _ = doer.Once(ctx, tt.sleepTime) }() } wait.Wait() assertBetween(t, doer.Successes, tt.expectedSuccessesRange, "success count") assertBetween(t, len(doer.Errors), tt.expectedErrorRange, "error count") for _, errMsg := range tt.expectedErrors { assert.Contains(t, doer.Errors, errMsg) } }) } } func TestNeedlessUnlock(t *testing.T) { t.Parallel() locker := NewLocker(100*time.Millisecond, 200*time.Millisecond) assert.Panics(t, func() { locker.Unlock() }) } func TestDoubleUnlock(t *testing.T) { t.Parallel() locker := NewLocker(100*time.Millisecond, 200*time.Millisecond) require.NoError(t, locker.Lock(context.TODO())) assert.NotPanics(t, func() { locker.Unlock() }) assert.Panics(t, func() { locker.Unlock() }) } func TestLockerChaos(t *testing.T) { t.Parallel() doer := NewThingDoer() wait := sync.WaitGroup{} for i := 0; i < 100; i++ { wait.Add(1) go func() { defer wait.Done() ctx := context.TODO() if rand.Intn(100) > 20 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, time.Duration(rand.Intn(500))*time.Millisecond) defer cancel() } _ = doer.Once(ctx, time.Duration(rand.Intn(100))*time.Millisecond) }() } wait.Wait() assert.GreaterOrEqual(t, doer.Successes, 1, "successes") assert.GreaterOrEqual(t, len(doer.Errors), 1, "failures") } type thingDoer struct { locker *locker Successes int Errors []string errMu sync.Mutex } func NewThingDoer() *thingDoer { return &thingDoer{ locker: NewLocker(100*time.Millisecond, 200*time.Millisecond), Errors: make([]string, 0), } } func (doer *thingDoer) Once(ctx context.Context, d time.Duration) error { if err := doer.locker.Lock(ctx); err != nil { doer.errMu.Lock() defer doer.errMu.Unlock() doer.Errors = append(doer.Errors, err.Error()) return err } defer doer.locker.Unlock() // Note that we don't need to protect the success path with a mutext, that is the point of locker time.Sleep(d) doer.Successes += 1 return nil } // assertBetween is a wrapper over assert.GreateOrEqual and assert.LessOrEqual. We use it to provide small ranges for // expected test results. This is because GitHub Actions is prone to weird timing issues and slowdowns func assertBetween(t *testing.T, actual int, r [2]int, msg string) { assert.GreaterOrEqual(t, actual, r[0], msg) assert.LessOrEqual(t, actual, r[1], msg) } ================================================ FILE: mock/osquery.go ================================================ // Automatically generated by mockimpl. DO NOT EDIT! package mock import ( "context" "github.com/osquery/osquery-go/gen/osquery" ) var _ osquery.ExtensionManager = (*ExtensionManager)(nil) type CloseFunc func() type PingFunc func(ctx context.Context) (*osquery.ExtensionStatus, error) type CallFunc func(ctx context.Context, registry string, item string, req osquery.ExtensionPluginRequest) (*osquery.ExtensionResponse, error) type ShutdownFunc func(ctx context.Context) error type ExtensionsFunc func(ctx context.Context) (osquery.InternalExtensionList, error) type RegisterExtensionFunc func(ctx context.Context, info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) type DeregisterExtensionFunc func(ctx context.Context, uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) type OptionsFunc func(ctx context.Context) (osquery.InternalOptionList, error) type QueryFunc func(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) type GetQueryColumnsFunc func(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) type ExtensionManager struct { CloseFunc CloseFunc CloseFuncInvoked bool PingFunc PingFunc PingFuncInvoked bool CallFunc CallFunc CallFuncInvoked bool ShutdownFunc ShutdownFunc ShutdownFuncInvoked bool ExtensionsFunc ExtensionsFunc ExtensionsFuncInvoked bool RegisterExtensionFunc RegisterExtensionFunc RegisterExtensionFuncInvoked bool DeregisterExtensionFunc DeregisterExtensionFunc DeregisterExtensionFuncInvoked bool OptionsFunc OptionsFunc OptionsFuncInvoked bool QueryFunc QueryFunc QueryFuncInvoked bool GetQueryColumnsFunc GetQueryColumnsFunc GetQueryColumnsFuncInvoked bool } func (m *ExtensionManager) Close() { m.CloseFuncInvoked = true m.CloseFunc() } func (m *ExtensionManager) Ping(ctx context.Context) (*osquery.ExtensionStatus, error) { m.PingFuncInvoked = true return m.PingFunc(ctx) } func (m *ExtensionManager) Call(ctx context.Context, registry string, item string, req osquery.ExtensionPluginRequest) (*osquery.ExtensionResponse, error) { m.CallFuncInvoked = true return m.CallFunc(ctx, registry, item, req) } func (m *ExtensionManager) Shutdown(ctx context.Context) error { m.ShutdownFuncInvoked = true return m.ShutdownFunc(ctx) } func (m *ExtensionManager) Extensions(ctx context.Context) (osquery.InternalExtensionList, error) { m.ExtensionsFuncInvoked = true return m.ExtensionsFunc(ctx) } func (m *ExtensionManager) RegisterExtension(ctx context.Context, info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) { m.RegisterExtensionFuncInvoked = true return m.RegisterExtensionFunc(ctx, info, registry) } func (m *ExtensionManager) DeregisterExtension(ctx context.Context, uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) { m.DeregisterExtensionFuncInvoked = true return m.DeregisterExtensionFunc(ctx, uuid) } func (m *ExtensionManager) Options(ctx context.Context) (osquery.InternalOptionList, error) { m.OptionsFuncInvoked = true return m.OptionsFunc(ctx) } func (m *ExtensionManager) Query(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) { m.QueryFuncInvoked = true return m.QueryFunc(ctx, sql) } func (m *ExtensionManager) GetQueryColumns(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) { m.GetQueryColumnsFuncInvoked = true return m.GetQueryColumnsFunc(ctx, sql) } ================================================ FILE: mock_manager.go ================================================ // Automatically generated by mockimpl. DO NOT EDIT! package osquery import "github.com/osquery/osquery-go/gen/osquery" var _ ExtensionManager = (*MockExtensionManager)(nil) type CloseFunc func() type PingFunc func() (*osquery.ExtensionStatus, error) type CallFunc func(registry string, item string, req osquery.ExtensionPluginRequest) (*osquery.ExtensionResponse, error) type ExtensionsFunc func() (osquery.InternalExtensionList, error) type RegisterExtensionFunc func(info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) type DeregisterExtensionFunc func(uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) type OptionsFunc func() (osquery.InternalOptionList, error) type QueryFunc func(sql string) (*osquery.ExtensionResponse, error) type GetQueryColumnsFunc func(sql string) (*osquery.ExtensionResponse, error) type MockExtensionManager struct { CloseFunc CloseFunc CloseFuncInvoked bool PingFunc PingFunc PingFuncInvoked bool CallFunc CallFunc CallFuncInvoked bool ExtensionsFunc ExtensionsFunc ExtensionsFuncInvoked bool RegisterExtensionFunc RegisterExtensionFunc RegisterExtensionFuncInvoked bool DeRegisterExtensionFunc DeregisterExtensionFunc DeRegisterExtensionFuncInvoked bool OptionsFunc OptionsFunc OptionsFuncInvoked bool QueryFunc QueryFunc QueryFuncInvoked bool GetQueryColumnsFunc GetQueryColumnsFunc GetQueryColumnsFuncInvoked bool } func (m *MockExtensionManager) Close() { m.CloseFuncInvoked = true m.CloseFunc() } func (m *MockExtensionManager) Ping() (*osquery.ExtensionStatus, error) { m.PingFuncInvoked = true return m.PingFunc() } func (m *MockExtensionManager) Call(registry string, item string, req osquery.ExtensionPluginRequest) (*osquery.ExtensionResponse, error) { m.CallFuncInvoked = true return m.CallFunc(registry, item, req) } func (m *MockExtensionManager) Extensions() (osquery.InternalExtensionList, error) { m.ExtensionsFuncInvoked = true return m.ExtensionsFunc() } func (m *MockExtensionManager) RegisterExtension(info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) { m.RegisterExtensionFuncInvoked = true return m.RegisterExtensionFunc(info, registry) } func (m *MockExtensionManager) DeregisterExtension(uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) { m.DeRegisterExtensionFuncInvoked = true return m.DeRegisterExtensionFunc(uuid) } func (m *MockExtensionManager) Options() (osquery.InternalOptionList, error) { m.OptionsFuncInvoked = true return m.OptionsFunc() } func (m *MockExtensionManager) Query(sql string) (*osquery.ExtensionResponse, error) { m.QueryFuncInvoked = true return m.QueryFunc(sql) } func (m *MockExtensionManager) GetQueryColumns(sql string) (*osquery.ExtensionResponse, error) { m.GetQueryColumnsFuncInvoked = true return m.GetQueryColumnsFunc(sql) } ================================================ FILE: osquery.thrift ================================================ namespace cpp osquery.extensions /// Registry operations use a registry name, plugin name, request/response. typedef map ExtensionPluginRequest typedef list> ExtensionPluginResponse /// Extensions should request osquery options to set active registries and /// bootstrap any config/logger plugins. struct InternalOptionInfo { 1:string value, 2:string default_value, 3:string type, } /// Each option (CLI flag) has a unique name. typedef map InternalOptionList /// When communicating extension metadata, use a thrift-internal structure. struct InternalExtensionInfo { 1:string name, 2:string version, 3:string sdk_version, 4:string min_sdk_version, } /// Unique ID for each extension. typedef i64 ExtensionRouteUUID /// A map from each plugin name to its optional route information. typedef map ExtensionRouteTable /// A map from each registry name. typedef map ExtensionRegistry /// A map from each extension's unique ID to its map of registries. typedef map InternalExtensionList enum ExtensionCode { EXT_SUCCESS = 0, EXT_FAILED = 1, EXT_FATAL = 2, } /// Most communication uses the Status return type. struct ExtensionStatus { 1:i32 code, 2:string message, /// Add a thrift Status parameter identifying the request/response. 3:ExtensionRouteUUID uuid, } struct ExtensionResponse { 1:ExtensionStatus status, 2:ExtensionPluginResponse response, } exception ExtensionException { 1:i32 code, 2:string message, 3:ExtensionRouteUUID uuid, } service Extension { /// Ping to/from an extension and extension manager for metadata. ExtensionStatus ping(), /// Call an extension (or core) registry plugin. ExtensionResponse call( /// The registry name (e.g., config, logger, table, etc). 1:string registry, /// The registry item name (plugin name). 2:string item, /// The thrift-equivilent of an osquery::PluginRequest. 3:ExtensionPluginRequest request), /// Request that an extension shutdown (does not apply to managers). void shutdown(), } /// The extension manager is run by the osquery core process. service ExtensionManager extends Extension { /// Return the list of active registered extensions. InternalExtensionList extensions(), /// Return the list of bootstrap or configuration options. InternalOptionList options(), /// The API endpoint used by an extension to register its plugins. ExtensionStatus registerExtension( 1:InternalExtensionInfo info, 2:ExtensionRegistry registry), ExtensionStatus deregisterExtension( 1:ExtensionRouteUUID uuid, ), /// Allow an extension to query using an SQL string. ExtensionResponse query( 1:string sql, ), /// Allow an extension to introspect into SQL used in a parsed query. ExtensionResponse getQueryColumns( 1:string sql, ), } ================================================ FILE: plugin/config/config.go ================================================ // Package config creates an osquery configuration plugin. // // See https://osquery.readthedocs.io/en/latest/development/config-plugins/ for more. package config import ( "context" "github.com/osquery/osquery-go/gen/osquery" "github.com/osquery/osquery-go/traces" ) // GenerateConfigsFunc returns the configurations generated by this plugin. // The returned map should use the source name as key, and the config // JSON as values. The context argument can optionally be used for // cancellation in long-running operations. type GenerateConfigsFunc func(ctx context.Context) (map[string]string, error) // Plugin is an osquery configuration plugin. Plugin implements the OsqueryPlugin // interface. type Plugin struct { name string generate GenerateConfigsFunc } // NewConfigPlugin takes a value that implements ConfigPlugin and wraps it with // the appropriate methods to satisfy the OsqueryPlugin interface. Use this to // easily create configuration plugins. func NewPlugin(name string, fn GenerateConfigsFunc) *Plugin { return &Plugin{name: name, generate: fn} } func (t *Plugin) Name() string { return t.name } // Registry name for config plugins const configRegistryName = "config" func (t *Plugin) RegistryName() string { return configRegistryName } func (t *Plugin) Routes() osquery.ExtensionPluginResponse { return osquery.ExtensionPluginResponse{} } func (t *Plugin) Ping() osquery.ExtensionStatus { return osquery.ExtensionStatus{Code: 0, Message: "OK"} } // Key that the request method is stored under const requestActionKey = "action" // Action value used when config is requested const genConfigAction = "genConfig" func (t *Plugin) Call(ctx context.Context, request osquery.ExtensionPluginRequest) osquery.ExtensionResponse { ctx, span := traces.StartSpan(ctx, "Config.Call", "action", request[requestActionKey]) defer span.End() switch request[requestActionKey] { case genConfigAction: configs, err := t.generate(ctx) if err != nil { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "error getting config: " + err.Error(), }, } } return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{Code: 0, Message: "OK"}, Response: osquery.ExtensionPluginResponse{configs}, } default: return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "unknown action: " + request["action"], }, } } } func (t *Plugin) Shutdown() {} ================================================ FILE: plugin/config/config_test.go ================================================ package config import ( "context" "errors" "testing" "github.com/osquery/osquery-go/gen/osquery" "github.com/stretchr/testify/assert" ) var StatusOK = osquery.ExtensionStatus{Code: 0, Message: "OK"} func TestConfigPlugin(t *testing.T) { var called bool plugin := NewPlugin("mock", func(context.Context) (map[string]string, error) { called = true return map[string]string{ "conf1": "foobar", }, nil }) // Basic methods assert.Equal(t, "config", plugin.RegistryName()) assert.Equal(t, "mock", plugin.Name()) assert.Equal(t, StatusOK, plugin.Ping()) assert.Equal(t, osquery.ExtensionPluginResponse{}, plugin.Routes()) // Call with good action resp := plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "genConfig"}) assert.True(t, called) assert.Equal(t, &StatusOK, resp.Status) assert.Equal(t, osquery.ExtensionPluginResponse{{"conf1": "foobar"}}, resp.Response) } func TestConfigPluginErrors(t *testing.T) { var called bool plugin := NewPlugin("mock", func(context.Context) (map[string]string, error) { called = true return nil, errors.New("foobar") }) // Call with bad actions assert.Equal(t, int32(1), plugin.Call(context.Background(), osquery.ExtensionPluginRequest{}).Status.Code) assert.False(t, called) assert.Equal(t, int32(1), plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "bad"}).Status.Code) assert.False(t, called) // Call with good action but generate fails resp := plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "genConfig"}) assert.True(t, called) assert.Equal(t, int32(1), resp.Status.Code) assert.Equal(t, "error getting config: foobar", resp.Status.Message) } ================================================ FILE: plugin/distributed/distributed.go ================================================ // Package distributed creates an osquery distributed query plugin. package distributed import ( "context" "encoding/json" "errors" "fmt" "reflect" "strconv" "strings" "github.com/osquery/osquery-go/gen/osquery" "github.com/osquery/osquery-go/traces" ) // GetQueriesResult contains the information about which queries the // distributed system should run. type GetQueriesResult struct { // Queries is a map from query name to query SQL Queries map[string]string `json:"queries"` // Discovery is used for "discovery" queries in the distributed // system. When used, discovery queries should be specified with query // name as the key and the discover query SQL as the value. If this is // nonempty, only queries for which the associated discovery query // returns results will be run in osquery. Discovery map[string]string `json:"discovery,omitempty"` // AccelerateSeconds can be specified to have "accelerated" checkins // for a given number of seconds after this checkin. Currently this // means that checkins will occur every 5 seconds. AccelerateSeconds int `json:"accelerate,omitempty"` } // GetQueriesFunc returns the queries that should be executed. // The returned map should include the query name as the keys, and the query // text as values. Results will be returned corresponding to the provided name. // The context argument can optionally be used for cancellation in long-running // operations. type GetQueriesFunc func(ctx context.Context) (*GetQueriesResult, error) // Result contains the status and results for a distributed query. type Result struct { // QueryName is the name that was originally provided for the query. QueryName string `json:"query_name"` // Status is an integer status code for the query execution (0 = OK) Status int `json:"status"` // Rows is the result rows of the query. Rows []map[string]string `json:"rows"` // QueryStats are the stats about the execution of the given query QueryStats *Stats `json:"stats"` // Message is the message string indicating the status of the query Message string `json:"message"` } // WriteResultsFunc writes the results of the executed distributed queries. The // query results will be serialized JSON in the results map with the query name // as the key. type WriteResultsFunc func(ctx context.Context, results []Result) error // Plugin is an osquery configuration plugin. Plugin implements the OsqueryPlugin // interface. type Plugin struct { name string getQueries GetQueriesFunc writeResults WriteResultsFunc } // NewPlugin takes the distributed query functions and returns a struct // implementing the OsqueryPlugin interface. Use this to wrap the appropriate // functions into an osquery plugin. func NewPlugin(name string, getQueries GetQueriesFunc, writeResults WriteResultsFunc) *Plugin { return &Plugin{name: name, getQueries: getQueries, writeResults: writeResults} } func (t *Plugin) Name() string { return t.name } // Registry name for distributed plugins const distributedRegistryName = "distributed" func (t *Plugin) RegistryName() string { return distributedRegistryName } func (t *Plugin) Routes() osquery.ExtensionPluginResponse { return osquery.ExtensionPluginResponse{} } func (t *Plugin) Ping() osquery.ExtensionStatus { return osquery.ExtensionStatus{Code: 0, Message: "OK"} } // Key that the request method is stored under const requestActionKey = "action" // Action value used when queries are requested const getQueriesAction = "getQueries" // Action value used when results are written const writeResultsAction = "writeResults" // Key that results are stored under const requestResultKey = "results" // OsqueryInt handles unmarshaling integers in noncanonical osquery json. type OsqueryInt int // UnmarshalJSON marshals a json string that is convertable to an int, for // example "234" -> 234. func (oi *OsqueryInt) UnmarshalJSON(buff []byte) error { s := string(buff) if strings.Contains(s, `"`) { unquoted, err := strconv.Unquote(s) if err != nil { return &json.UnmarshalTypeError{ Value: string(buff), Type: reflect.TypeOf(oi), Struct: "statuses", } } s = unquoted } if len(s) == 0 { *oi = OsqueryInt(0) return nil } parsedInt, err := strconv.ParseInt(s, 10, 32) if err != nil { return &json.UnmarshalTypeError{ Value: string(buff), Type: reflect.TypeOf(oi), Struct: "statuses", } } *oi = OsqueryInt(parsedInt) return nil } // ResultsStruct is used for unmarshalling the results passed from osquery. type ResultsStruct struct { Queries map[string][]map[string]string `json:"queries"` Statuses map[string]OsqueryInt `json:"statuses"` Stats map[string]Stats `json:"stats"` Messages map[string]string `json:"messages"` } // Stats holds performance stats about the execution of a given query. type Stats struct { WallTimeMs OsqueryInt `json:"wall_time_ms"` UserTime OsqueryInt `json:"user_time"` SystemTime OsqueryInt `json:"system_time"` Memory OsqueryInt `json:"memory"` } // UnmarshalJSON turns structurally inconsistent osquery json into a ResultsStruct. func (rs *ResultsStruct) UnmarshalJSON(buff []byte) error { emptyRow := []map[string]string{} rs.Queries = make(map[string][]map[string]string) rs.Statuses = make(map[string]OsqueryInt) rs.Messages = make(map[string]string) // Queries can be []map[string]string OR an empty string // so we need to deal with an interface to accommodate two types intermediate := struct { Queries map[string]interface{} `json:"queries"` Statuses map[string]OsqueryInt `json:"statuses"` Stats map[string]Stats `json:"stats"` Messages map[string]string `json:"messages"` }{} if err := json.Unmarshal(buff, &intermediate); err != nil { return err } for queryName, status := range intermediate.Statuses { rs.Statuses[queryName] = status // Sometimes we have a status but don't have a corresponding // result. queryResult, ok := intermediate.Queries[queryName] if !ok { rs.Queries[queryName] = emptyRow continue } // Deal with structurally inconsistent results, sometimes a query // without any results is just a name with an empty string. switch val := queryResult.(type) { case string: rs.Queries[queryName] = emptyRow case []interface{}: results, err := convertRows(val) if err != nil { return err } rs.Queries[queryName] = results default: return fmt.Errorf("results for %q unknown type", queryName) } } // Stats and messages don't require any format changes rs.Stats = intermediate.Stats rs.Messages = intermediate.Messages return nil } func (rs *ResultsStruct) toResults() ([]Result, error) { var results []Result for queryName, rows := range rs.Queries { result := Result{ QueryName: queryName, Rows: rows, Status: int(rs.Statuses[queryName]), QueryStats: nil, } if stats, ok := rs.Stats[queryName]; ok { result.QueryStats = &stats } if msg, ok := rs.Messages[queryName]; ok { result.Message = msg } results = append(results, result) } return results, nil } func convertRows(rows []interface{}) ([]map[string]string, error) { var results []map[string]string for _, intf := range rows { row, ok := intf.(map[string]interface{}) if !ok { return nil, errors.New("invalid row type for query") } result := make(map[string]string) for col, val := range row { sval, ok := val.(string) if !ok { return nil, fmt.Errorf("invalid type for col %q", col) } result[col] = sval } results = append(results, result) } return results, nil } func (t *Plugin) Call(ctx context.Context, request osquery.ExtensionPluginRequest) osquery.ExtensionResponse { ctx, span := traces.StartSpan(ctx, "Distributed.Call", "action", request[requestActionKey]) defer span.End() switch request[requestActionKey] { case getQueriesAction: queries, err := t.getQueries(ctx) if err != nil { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "error getting queries: " + err.Error(), }, } } queryJSON, err := json.Marshal(queries) if err != nil { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "error marshalling queries: " + err.Error(), }, } } return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{Code: 0, Message: "OK"}, Response: osquery.ExtensionPluginResponse{map[string]string{"results": string(queryJSON)}}, } case writeResultsAction: var rs ResultsStruct if err := json.Unmarshal([]byte(request[requestResultKey]), &rs); err != nil { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "error unmarshalling results: " + err.Error(), }, } } results, err := rs.toResults() if err != nil { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "error writing results: " + err.Error(), }, } } // invoke callback err = t.writeResults(ctx, results) if err != nil { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "error writing results: " + err.Error(), }, } } return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{Code: 0, Message: "OK"}, Response: osquery.ExtensionPluginResponse{}, } default: return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "unknown action: " + request["action"], }, } } } func (t *Plugin) Shutdown() {} ================================================ FILE: plugin/distributed/distributed_test.go ================================================ package distributed import ( "bytes" "context" "encoding/json" "errors" "fmt" "sort" "testing" "github.com/osquery/osquery-go/gen/osquery" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var StatusOK = osquery.ExtensionStatus{Code: 0, Message: "OK"} func TestDistributedPlugin(t *testing.T) { var getCalled, writeCalled bool var results []Result plugin := NewPlugin( "mock", func(context.Context) (*GetQueriesResult, error) { getCalled = true return &GetQueriesResult{ Queries: map[string]string{ "query1": "select iso_8601 from time", "query2": "select version from osquery_info", "query3": "select foo from bar", }, }, nil }, func(ctx context.Context, res []Result) error { writeCalled = true results = res return nil }, ) // Basic methods assert.Equal(t, "distributed", plugin.RegistryName()) assert.Equal(t, "mock", plugin.Name()) assert.Equal(t, StatusOK, plugin.Ping()) assert.Equal(t, osquery.ExtensionPluginResponse{}, plugin.Routes()) // Call getQueries resp := plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "getQueries"}) assert.True(t, getCalled) assert.False(t, writeCalled) assert.Equal(t, &StatusOK, resp.Status) if assert.Len(t, resp.Response, 1) { assert.JSONEq(t, `{"queries": {"query1": "select iso_8601 from time", "query2": "select version from osquery_info", "query3": "select foo from bar"}}`, resp.Response[0]["results"]) } // Call writeResults getCalled = false resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "writeResults", "results": `{"queries":{"query1":[{"iso_8601":"2017-07-10T22:08:40Z"}],"query2":[{"version":"2.4.0"}]},"statuses":{"query1":"0","query2":"0","query3":"1"}, "stats":{"query1":{"wall_time_ms": 1, "user_time": 1, "system_time": 1, "memory": 1},"query2":{"wall_time_ms": 2, "user_time": 2, "system_time": 2, "memory": 2},"query3":{"wall_time_ms": 3, "user_time": 3, "system_time": 3, "memory": 3}}}`}) assert.False(t, getCalled) assert.True(t, writeCalled) assert.Equal(t, &StatusOK, resp.Status) // Ensure correct ordering for comparison sort.Slice(results, func(i, j int) bool { return results[i].QueryName < results[j].QueryName }) assert.Equal(t, []Result{ {"query1", 0, []map[string]string{{"iso_8601": "2017-07-10T22:08:40Z"}}, &Stats{WallTimeMs: 1, UserTime: 1, SystemTime: 1, Memory: 1}, ""}, {"query2", 0, []map[string]string{{"version": "2.4.0"}}, &Stats{WallTimeMs: 2, UserTime: 2, SystemTime: 2, Memory: 2}, ""}, {"query3", 1, []map[string]string{}, &Stats{WallTimeMs: 3, UserTime: 3, SystemTime: 3, Memory: 3}, ""}, }, results) // Call writeResults -- no stats attached getCalled = false resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "writeResults", "results": `{"queries":{"query1":[{"iso_8601":"2017-07-10T22:08:40Z"}],"query2":[{"version":"2.4.0"}]},"statuses":{"query1":"0","query2":"0","query3":"1"}}`}) assert.False(t, getCalled) assert.True(t, writeCalled) assert.Equal(t, &StatusOK, resp.Status) // Ensure correct ordering for comparison sort.Slice(results, func(i, j int) bool { return results[i].QueryName < results[j].QueryName }) assert.Equal(t, []Result{ {"query1", 0, []map[string]string{{"iso_8601": "2017-07-10T22:08:40Z"}}, nil, ""}, {"query2", 0, []map[string]string{{"version": "2.4.0"}}, nil, ""}, {"query3", 1, []map[string]string{}, nil, ""}, }, results) // Call writeResults -- with message included getCalled = false resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "writeResults", "results": `{"queries":{"query1":[{"iso_8601":"2017-07-10T22:08:40Z"}],"query2":[{"version":"2.4.0"}]},"statuses":{"query1":"0","query2":"0","query3":"1"}, "stats":{"query1":{"wall_time_ms": 1, "user_time": 1, "system_time": 1, "memory": 1},"query2":{"wall_time_ms": 2, "user_time": 2, "system_time": 2, "memory": 2},"query3":{"wall_time_ms": 3, "user_time": 3, "system_time": 3, "memory": 3}}, "messages": {"query3": "distributed query is denylisted"}}`}) assert.False(t, getCalled) assert.True(t, writeCalled) assert.Equal(t, &StatusOK, resp.Status) // Ensure correct ordering for comparison sort.Slice(results, func(i, j int) bool { return results[i].QueryName < results[j].QueryName }) assert.Equal(t, []Result{ {"query1", 0, []map[string]string{{"iso_8601": "2017-07-10T22:08:40Z"}}, &Stats{WallTimeMs: 1, UserTime: 1, SystemTime: 1, Memory: 1}, ""}, {"query2", 0, []map[string]string{{"version": "2.4.0"}}, &Stats{WallTimeMs: 2, UserTime: 2, SystemTime: 2, Memory: 2}, ""}, {"query3", 1, []map[string]string{}, &Stats{WallTimeMs: 3, UserTime: 3, SystemTime: 3, Memory: 3}, "distributed query is denylisted"}, }, results) } func TestDistributedPluginAccelerateDiscovery(t *testing.T) { plugin := NewPlugin( "mock", func(context.Context) (*GetQueriesResult, error) { return &GetQueriesResult{ Queries: map[string]string{ "query1": "select * from time", }, Discovery: map[string]string{ "query1": `select version from osquery_info where version = "2.4.0"`, }, AccelerateSeconds: 30, }, nil }, func(ctx context.Context, res []Result) error { return nil }, ) // Call getQueries resp := plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "getQueries"}) assert.Equal(t, &StatusOK, resp.Status) if assert.Len(t, resp.Response, 1) { assert.JSONEq(t, `{"queries": {"query1": "select * from time"}, "discovery": {"query1": "select version from osquery_info where version = \"2.4.0\""}, "accelerate": 30}`, resp.Response[0]["results"]) } } func TestDistributedPluginErrors(t *testing.T) { var getCalled, writeCalled bool plugin := NewPlugin( "mock", func(context.Context) (*GetQueriesResult, error) { getCalled = true return nil, errors.New("getQueries failed") }, func(ctx context.Context, res []Result) error { writeCalled = true return errors.New("writeResults failed") }, ) // Call with bad actions assert.Equal(t, int32(1), plugin.Call(context.Background(), osquery.ExtensionPluginRequest{}).Status.Code) assert.False(t, getCalled) assert.False(t, writeCalled) assert.Equal(t, int32(1), plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "bad"}).Status.Code) assert.False(t, getCalled) assert.False(t, writeCalled) // Call with good action but getQueries fails resp := plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "getQueries"}) assert.True(t, getCalled) assert.False(t, writeCalled) assert.Equal(t, int32(1), resp.Status.Code) assert.Equal(t, "error getting queries: getQueries failed", resp.Status.Message) getCalled = false // Call with good action but writeResults fails // Error unmarshalling results resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "writeResults", "results": "foobar"}) assert.False(t, getCalled) assert.False(t, writeCalled) assert.Equal(t, int32(1), resp.Status.Code) assert.Contains(t, resp.Status.Message, "error unmarshalling results") // Error converting status resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "writeResults", "results": `{"statuses": {"query1": "foo"}}`}) assert.False(t, getCalled) assert.False(t, writeCalled) assert.Equal(t, int32(1), resp.Status.Code) assert.Contains(t, resp.Status.Message, `error unmarshalling results: json: cannot unmarshal`) // Error unmarshalling results resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "writeResults", "results": "{}"}) assert.False(t, getCalled) assert.True(t, writeCalled) assert.Equal(t, int32(1), resp.Status.Code) assert.Contains(t, resp.Status.Message, "error writing results") } var rawJsonQuery = "{\"queries\":{\"kolide_detail_query_network_interface\":[{\"interface\":\"en0\",\"mac\":\"78:4f:43:9c:3c:8d\",\"type\":\"\",\"mtu\":\"1500\",\"metric\":\"0\",\"ipackets\":\"7071136\",\"opackets\":\"6408727\",\"ibytes\":\"1481456771\",\"obytes\":\"1633052673\",\"ierrors\":\"0\",\"oerrors\":\"0\",\"idrops\":\"0\",\"odrops\":\"0\",\"last_change\":\"1501077669\",\"description\":\"\",\"manufacturer\":\"\",\"connection_id\":\"\",\"connection_status\":\"\",\"enabled\":\"\",\"physical_adapter\":\"\",\"speed\":\"\",\"dhcp_enabled\":\"\",\"dhcp_lease_expires\":\"\",\"dhcp_lease_obtained\":\"\",\"dhcp_server\":\"\",\"dns_domain\":\"\",\"dns_domain_suffix_search_order\":\"\",\"dns_host_name\":\"\",\"dns_server_search_order\":\"\",\"interface\":\"en0\",\"address\":\"192.168.1.135\",\"mask\":\"255.255.255.0\",\"broadcast\":\"192.168.1.255\",\"point_to_point\":\"\",\"type\":\"\"}],\"kolide_detail_query_os_version\":[{\"name\":\"Mac OS X\",\"version\":\"10.12.6\",\"major\":\"10\",\"minor\":\"12\",\"patch\":\"6\",\"build\":\"16G29\",\"platform\":\"darwin\",\"platform_like\":\"darwin\",\"codename\":\"\"}],\"kolide_detail_query_osquery_flags\":[{\"name\":\"config_refresh\",\"value\":\"10\"},{\"name\":\"distributed_interval\",\"value\":\"10\"},{\"name\":\"logger_tls_period\",\"value\":\"10\"}],\"kolide_detail_query_osquery_info\":[{\"pid\":\"75680\",\"uuid\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"instance_id\":\"89f267fa-9a17-4a73-87d6-05197491f2e8\",\"version\":\"2.5.0\",\"config_hash\":\"960121acb9bcbb136ce49fe77000752f237fd0dd\",\"config_valid\":\"1\",\"extensions\":\"active\",\"build_platform\":\"darwin\",\"build_distro\":\"10.12\",\"start_time\":\"1502371429\",\"watcher\":\"75678\"}],\"kolide_detail_query_system_info\":[{\"hostname\":\"Johns-MacBook-Pro.local\",\"uuid\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"cpu_type\":\"x86_64h\",\"cpu_subtype\":\"Intel x86-64h Haswell\",\"cpu_brand\":\"Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz\",\"cpu_physical_cores\":\"4\",\"cpu_logical_cores\":\"8\",\"physical_memory\":\"17179869184\",\"hardware_vendor\":\"Apple Inc.\",\"hardware_model\":\"MacBookPro13,3\",\"hardware_version\":\"1.0\",\"hardware_serial\":\"C02SP067H040\",\"computer_name\":\"\",\"local_hostname\":\"Johns-MacBook-Pro\"}],\"kolide_detail_query_uptime\":[{\"days\":\"21\",\"hours\":\"18\",\"minutes\":\"44\",\"seconds\":\"28\",\"total_seconds\":\"1881868\"}],\"kolide_label_query_6\":[{\"1\":\"1\"}],\"kolide_label_query_9\":\"\",\"kolide_detail_query_network_interface\":[{\"interface\":\"en0\",\"mac\":\"78:4f:43:9c:3c:8d\",\"type\":\"\",\"mtu\":\"1500\",\"metric\":\"0\",\"ipackets\":\"7071178\",\"opackets\":\"6408775\",\"ibytes\":\"1481473778\",\"obytes\":\"1633061382\",\"ierrors\":\"0\",\"oerrors\":\"0\",\"idrops\":\"0\",\"odrops\":\"0\",\"last_change\":\"1501077680\",\"description\":\"\",\"manufacturer\":\"\",\"connection_id\":\"\",\"connection_status\":\"\",\"enabled\":\"\",\"physical_adapter\":\"\",\"speed\":\"\",\"dhcp_enabled\":\"\",\"dhcp_lease_expires\":\"\",\"dhcp_lease_obtained\":\"\",\"dhcp_server\":\"\",\"dns_domain\":\"\",\"dns_domain_suffix_search_order\":\"\",\"dns_host_name\":\"\",\"dns_server_search_order\":\"\",\"interface\":\"en0\",\"address\":\"192.168.1.135\",\"mask\":\"255.255.255.0\",\"broadcast\":\"192.168.1.255\",\"point_to_point\":\"\",\"type\":\"\"}],\"kolide_detail_query_os_version\":[{\"name\":\"Mac OS X\",\"version\":\"10.12.6\",\"major\":\"10\",\"minor\":\"12\",\"patch\":\"6\",\"build\":\"16G29\",\"platform\":\"darwin\",\"platform_like\":\"darwin\",\"codename\":\"\"}],\"kolide_detail_query_osquery_flags\":[{\"name\":\"config_refresh\",\"value\":\"10\"},{\"name\":\"distributed_interval\",\"value\":\"10\"},{\"name\":\"logger_tls_period\",\"value\":\"10\"}],\"kolide_detail_query_osquery_info\":[{\"pid\":\"75680\",\"uuid\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"instance_id\":\"89f267fa-9a17-4a73-87d6-05197491f2e8\",\"version\":\"2.5.0\",\"config_hash\":\"960121acb9bcbb136ce49fe77000752f237fd0dd\",\"config_valid\":\"1\",\"extensions\":\"active\",\"build_platform\":\"darwin\",\"build_distro\":\"10.12\",\"start_time\":\"1502371429\",\"watcher\":\"75678\"}],\"kolide_detail_query_system_info\":[{\"hostname\":\"Johns-MacBook-Pro.local\",\"uuid\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"cpu_type\":\"x86_64h\",\"cpu_subtype\":\"Intel x86-64h Haswell\",\"cpu_brand\":\"Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz\",\"cpu_physical_cores\":\"4\",\"cpu_logical_cores\":\"8\",\"physical_memory\":\"17179869184\",\"hardware_vendor\":\"Apple Inc.\",\"hardware_model\":\"MacBookPro13,3\",\"hardware_version\":\"1.0\",\"hardware_serial\":\"C02SP067H040\",\"computer_name\":\"\",\"local_hostname\":\"Johns-MacBook-Pro\"}],\"kolide_detail_query_uptime\":[{\"days\":\"21\",\"hours\":\"18\",\"minutes\":\"44\",\"seconds\":\"38\",\"total_seconds\":\"1881878\"}],\"kolide_label_query_6\":[{\"1\":\"1\"}],\"kolide_label_query_9\":\"\",\"kolide_detail_query_network_interface\":[{\"interface\":\"en0\",\"mac\":\"78:4f:43:9c:3c:8d\",\"type\":\"\",\"mtu\":\"1500\",\"metric\":\"0\",\"ipackets\":\"7071216\",\"opackets\":\"6408814\",\"ibytes\":\"1481486677\",\"obytes\":\"1633066361\",\"ierrors\":\"0\",\"oerrors\":\"0\",\"idrops\":\"0\",\"odrops\":\"0\",\"last_change\":\"1501077688\",\"description\":\"\",\"manufacturer\":\"\",\"connection_id\":\"\",\"connection_status\":\"\",\"enabled\":\"\",\"physical_adapter\":\"\",\"speed\":\"\",\"dhcp_enabled\":\"\",\"dhcp_lease_expires\":\"\",\"dhcp_lease_obtained\":\"\",\"dhcp_server\":\"\",\"dns_domain\":\"\",\"dns_domain_suffix_search_order\":\"\",\"dns_host_name\":\"\",\"dns_server_search_order\":\"\",\"interface\":\"en0\",\"address\":\"192.168.1.135\",\"mask\":\"255.255.255.0\",\"broadcast\":\"192.168.1.255\",\"point_to_point\":\"\",\"type\":\"\"}],\"kolide_detail_query_os_version\":[{\"name\":\"Mac OS X\",\"version\":\"10.12.6\",\"major\":\"10\",\"minor\":\"12\",\"patch\":\"6\",\"build\":\"16G29\",\"platform\":\"darwin\",\"platform_like\":\"darwin\",\"codename\":\"\"}],\"kolide_detail_query_osquery_flags\":[{\"name\":\"config_refresh\",\"value\":\"10\"},{\"name\":\"distributed_interval\",\"value\":\"10\"},{\"name\":\"logger_tls_period\",\"value\":\"10\"}],\"kolide_detail_query_osquery_info\":[{\"pid\":\"75680\",\"uuid\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"instance_id\":\"89f267fa-9a17-4a73-87d6-05197491f2e8\",\"version\":\"2.5.0\",\"config_hash\":\"960121acb9bcbb136ce49fe77000752f237fd0dd\",\"config_valid\":\"1\",\"extensions\":\"active\",\"build_platform\":\"darwin\",\"build_distro\":\"10.12\",\"start_time\":\"1502371429\",\"watcher\":\"75678\"}],\"kolide_detail_query_system_info\":[{\"hostname\":\"Johns-MacBook-Pro.local\",\"uuid\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"cpu_type\":\"x86_64h\",\"cpu_subtype\":\"Intel x86-64h Haswell\",\"cpu_brand\":\"Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz\",\"cpu_physical_cores\":\"4\",\"cpu_logical_cores\":\"8\",\"physical_memory\":\"17179869184\",\"hardware_vendor\":\"Apple Inc.\",\"hardware_model\":\"MacBookPro13,3\",\"hardware_version\":\"1.0\",\"hardware_serial\":\"C02SP067H040\",\"computer_name\":\"\",\"local_hostname\":\"Johns-MacBook-Pro\"}],\"kolide_detail_query_uptime\":[{\"days\":\"21\",\"hours\":\"18\",\"minutes\":\"44\",\"seconds\":\"49\",\"total_seconds\":\"1881889\"}],\"kolide_label_query_6\":[{\"1\":\"1\"}],\"kolide_label_query_9\":\"\"},\"statuses\":{\"kolide_detail_query_network_interface\":\"0\",\"kolide_detail_query_os_version\":\"0\",\"kolide_detail_query_osquery_flags\":\"0\",\"kolide_detail_query_osquery_info\":\"0\",\"kolide_detail_query_system_info\":\"0\",\"kolide_detail_query_uptime\":\"0\",\"kolide_label_query_6\":\"0\",\"kolide_label_query_9\":\"0\"}}\n" func TestUnmarshalResults(t *testing.T) { var rs ResultsStruct err := json.NewDecoder(bytes.NewBufferString(rawJsonQuery)).Decode(&rs) require.Nil(t, err) results, err := rs.toResults() require.Nil(t, err) assert.Len(t, results, 8) } func TestUnmarshalStatus(t *testing.T) { testCases := []struct { json []byte success bool expected OsqueryInt }{ {[]byte{}, true, 0}, {[]byte(`""`), true, 0}, {[]byte(`"23"`), true, 23}, {[]byte(`"0000"`), true, 0}, {[]byte(`"-12"`), true, -12}, {[]byte(`"0"`), true, 0}, {[]byte(`"foo"`), false, 0}, {[]byte(`0`), true, 0}, {[]byte(`1`), true, 1}, } for i, testCase := range testCases { t.Run(fmt.Sprintf("#%.2d", i), func(t *testing.T) { var i OsqueryInt err := i.UnmarshalJSON(testCase.json) require.Equal(t, testCase.success, (err == nil), fmt.Sprintf("Trying to convert %s", string(testCase.json))) assert.Equal(t, testCase.expected, i) }) } } func TestHandleResults(t *testing.T) { called := false var results []Result plugin := NewPlugin( "mock", nil, func(ctx context.Context, res []Result) error { called = true results = res return nil }, ) resp := plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "writeResults", "results": rawJsonQuery}) require.True(t, called) assert.Len(t, results, 8) assert.Equal(t, &StatusOK, resp.Status) } ================================================ FILE: plugin/logger/logger.go ================================================ // Package logger creates an osquery logging plugin. // // See https://osquery.readthedocs.io/en/latest/development/logger-plugins/ for more. package logger import ( "bytes" "context" "encoding/json" "github.com/osquery/osquery-go/gen/osquery" ) // LogFunc is the logger function used by an osquery Logger plugin. // // The LogFunc should log the provided result string. The LogType // argument can be optionally used to log differently depending on the // type of log received. The context argument can optionally be used // for cancellation in long-running operations. type LogFunc func(ctx context.Context, typ LogType, log string) error // Plugin is an osquery logger plugin. // The Plugin struct implements the OsqueryPlugin interface. type Plugin struct { name string logFn LogFunc } // NewPlugin takes a value that implements LoggerPlugin and wraps it with // the appropriate methods to satisfy the OsqueryPlugin interface. Use this to // easily create plugins implementing osquery loggers. func NewPlugin(name string, fn LogFunc) *Plugin { return &Plugin{name: name, logFn: fn} } func (t *Plugin) Name() string { return t.name } func (t *Plugin) RegistryName() string { return "logger" } func (t *Plugin) Routes() osquery.ExtensionPluginResponse { return []map[string]string{} } func (t *Plugin) Ping() osquery.ExtensionStatus { return osquery.ExtensionStatus{Code: 0, Message: "OK"} } func (t *Plugin) Call(ctx context.Context, request osquery.ExtensionPluginRequest) osquery.ExtensionResponse { var err error if log, ok := request["string"]; ok { err = t.logFn(ctx, LogTypeString, log) } else if log, ok := request["snapshot"]; ok { err = t.logFn(ctx, LogTypeSnapshot, log) } else if log, ok := request["health"]; ok { err = t.logFn(ctx, LogTypeHealth, log) } else if log, ok := request["init"]; ok { err = t.logFn(ctx, LogTypeInit, log) } else if _, ok := request["status"]; ok { statusJSON := []byte(request["log"]) if len(statusJSON) == 0 { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "got empty status", }, } } // Dirty hack because osquery gives us malformed JSON. statusJSON = bytes.Replace(statusJSON, []byte(`"":`), []byte(``), -1) statusJSON[0] = '[' statusJSON[len(statusJSON)-1] = ']' var parsedStatuses []json.RawMessage if err := json.Unmarshal(statusJSON, &parsedStatuses); err != nil { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "error parsing status logs: " + err.Error(), }, } } for _, s := range parsedStatuses { err = t.logFn(ctx, LogTypeStatus, string(s)) } } else { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "unknown log request", }, } } if err != nil { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "error logging: " + err.Error(), }, } } return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{Code: 0, Message: "OK"}, Response: osquery.ExtensionPluginResponse{}, } } func (t *Plugin) Shutdown() {} // LogType encodes the type of log osquery is outputting. type LogType int const ( LogTypeString LogType = iota LogTypeSnapshot LogTypeHealth LogTypeInit LogTypeStatus ) // String implements the fmt.Stringer interface for LogType. func (l LogType) String() string { var typeString string switch l { case LogTypeString: typeString = "string" case LogTypeSnapshot: typeString = "snapshot" case LogTypeHealth: typeString = "health" case LogTypeInit: typeString = "init" case LogTypeStatus: typeString = "status" default: typeString = "unknown" } return typeString } ================================================ FILE: plugin/logger/logger_test.go ================================================ package logger import ( "context" "errors" "testing" "github.com/osquery/osquery-go/gen/osquery" "github.com/stretchr/testify/assert" ) type mockLoggerPlugin struct { NameFunc func() string LogStringFunc func(context.Context, LogType, string) error } func (m *mockLoggerPlugin) Name() string { return m.NameFunc() } func (m *mockLoggerPlugin) LogString(ctx context.Context, typ LogType, log string) error { return m.LogStringFunc(ctx, typ, log) } func TestLoggerPlugin(t *testing.T) { var calledType LogType var calledLog string plugin := NewPlugin("mock", func(ctx context.Context, typ LogType, log string) error { calledType = typ calledLog = log return nil }) StatusOK := osquery.ExtensionStatus{Code: 0, Message: "OK"} // Basic methods assert.Equal(t, "logger", plugin.RegistryName()) assert.Equal(t, "mock", plugin.Name()) assert.Equal(t, StatusOK, plugin.Ping()) assert.Equal(t, osquery.ExtensionPluginResponse{}, plugin.Routes()) // Log string resp := plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"string": "logged string"}) assert.Equal(t, &StatusOK, resp.Status) assert.Equal(t, LogTypeString, calledType) assert.Equal(t, "logged string", calledLog) // Log snapshot resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"snapshot": "logged snapshot"}) assert.Equal(t, &StatusOK, resp.Status) assert.Equal(t, LogTypeSnapshot, calledType) assert.Equal(t, "logged snapshot", calledLog) // Log health resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"health": "logged health"}) assert.Equal(t, &StatusOK, resp.Status) assert.Equal(t, LogTypeHealth, calledType) assert.Equal(t, "logged health", calledLog) // Log init resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"init": "logged init"}) assert.Equal(t, &StatusOK, resp.Status) assert.Equal(t, LogTypeInit, calledType) assert.Equal(t, "logged init", calledLog) // Log status resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"status": "true", "log": `{"":{"s":"0","f":"events.cpp","i":"828","m":"Event publisher failed setup: kernel: Cannot access \/dev\/osquery"},"":{"s":"0","f":"events.cpp","i":"828","m":"Event publisher failed setup: scnetwork: Publisher not used"},"":{"s":"0","f":"scheduler.cpp","i":"74","m":"Executing scheduled query macos_kextstat: SELECT * FROM time"}}`}) assert.Equal(t, &StatusOK, resp.Status) assert.Equal(t, LogTypeStatus, calledType) assert.Equal(t, `{"s":"0","f":"scheduler.cpp","i":"74","m":"Executing scheduled query macos_kextstat: SELECT * FROM time"}`, calledLog) } func TestLogPluginErrors(t *testing.T) { var called bool plugin := NewPlugin("mock", func(ctx context.Context, typ LogType, log string) error { called = true return errors.New("foobar") }) // Call with bad actions assert.Equal(t, int32(1), plugin.Call(context.Background(), osquery.ExtensionPluginRequest{}).Status.Code) assert.False(t, called) assert.Equal(t, int32(1), plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "bad"}).Status.Code) assert.False(t, called) // Call with empty status assert.Equal(t, int32(1), plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"status": "true", "log": ""}).Status.Code) assert.False(t, called) // Call with good action but logging fails resp := plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"string": "logged string"}) assert.True(t, called) assert.Equal(t, int32(1), resp.Status.Code) assert.Equal(t, "error logging: foobar", resp.Status.Message) } ================================================ FILE: plugin/table/column.go ================================================ package table import ( "encoding/json" "strings" ) // ColumnDefinition defines the relevant information for a column in a table // plugin. Name and Type are mandatory. Prefer using the *Column helpers to // create ColumnDefinition structs. type ColumnDefinition struct { Name string `json:"name"` Type ColumnType `json:"type"` Description string `json:"description,omitempty"` Notes string `json:"notes,omitempty"` // Options from https://github.com/osquery/osquery/blob/master/osquery/core/sql/column.h#L37 Index bool `json:"index"` Required bool `json:"required"` Additional bool `json:"additional"` Optimized bool `json:"optimized"` Hidden bool `json:"hidden"` } // ColumnType is a strongly typed representation of the data type string for a // column definition. The named constants should be used. type ColumnType string // The following column types are defined in osquery tables.h. const ( ColumnTypeUnknown ColumnType = "UNKNOWN" ColumnTypeText ColumnType = "TEXT" ColumnTypeInteger ColumnType = "INTEGER" ColumnTypeBigInt ColumnType = "BIGINT" ColumnTypeUnsignedBigInt ColumnType = "UNSIGNED BIGINT" ColumnTypeDouble ColumnType = "DOUBLE" ColumnTypeBlob ColumnType = "BLOB" ) // jsonString returns the value used when marshaling ColumnType to JSON // (lowercase, spaces as underscores). func (ct ColumnType) jsonString() string { return strings.ReplaceAll(strings.ToLower(string(ct)), " ", "_") } // MarshalJSON implements json.Marshaler. func (ct ColumnType) MarshalJSON() ([]byte, error) { return json.Marshal(ct.jsonString()) } type ColumnOpt func(*ColumnDefinition) // TextColumn is a helper for defining columns containing strings. func TextColumn(name string, opts ...ColumnOpt) ColumnDefinition { return NewColumn(name, ColumnTypeText, opts...) } // IntegerColumn is a helper for defining columns containing integers. func IntegerColumn(name string, opts ...ColumnOpt) ColumnDefinition { return NewColumn(name, ColumnTypeInteger, opts...) } // BigIntColumn is a helper for defining columns containing big integers. func BigIntColumn(name string, opts ...ColumnOpt) ColumnDefinition { return NewColumn(name, ColumnTypeBigInt, opts...) } // DoubleColumn is a helper for defining columns containing floating point // values. func DoubleColumn(name string, opts ...ColumnOpt) ColumnDefinition { return NewColumn(name, ColumnTypeDouble, opts...) } // NewColumn returns a ColumnDefinition for the specified column. func NewColumn(name string, ctype ColumnType, opts ...ColumnOpt) ColumnDefinition { cd := ColumnDefinition{ Name: name, Type: ctype, } for _, opt := range opts { opt(&cd) } return cd } // IndexColumn is a functional argument to declare this as an indexed // column. Depending on implementation, this can significantly change // performance. See osquery source code for more information. func IndexColumn() ColumnOpt { return func(cd *ColumnDefinition) { cd.Index = true } } // RequiredColumn is a functional argument that sets this as a // required column. sqlite will not process queries, if a required // column is missing. See osquery source code for more information. func RequiredColumn() ColumnOpt { return func(cd *ColumnDefinition) { cd.Required = true } } // AdditionalColumn is a functional argument that sets this as an // additional column. See osquery source code for more information. func AdditionalColumn() ColumnOpt { return func(cd *ColumnDefinition) { cd.Additional = true } } // OptimizedColumn is a functional argument that sets this as an // optimized column. See osquery source code for more information. func OptimizedColumn() ColumnOpt { return func(cd *ColumnDefinition) { cd.Optimized = true } } // HiddenColumn is a functional argument that sets this as a // hidden column. This omits it from `select *` queries. See osquery source code for more information. func HiddenColumn() ColumnOpt { return func(cd *ColumnDefinition) { cd.Hidden = true } } // ColumnDescription sets the column description. This is not // currently part of the underlying osquery api, it is here for human // consumption. It may become part of osquery spec generation. func ColumnDescription(d string) ColumnOpt { return func(cd *ColumnDefinition) { cd.Description = d } } // Options returns the bitmask representation of the boolean column // options. This uses the values as encoded in // https://github.com/osquery/osquery/blob/master/osquery/core/sql/column.h#L37 func (c *ColumnDefinition) Options() uint8 { optionsBitmask := uint8(0) // We can skip the 0: default case, because it's what you get if nothing is set. optionValues := map[uint8]bool{ 1: c.Index, 2: c.Required, 4: c.Additional, 8: c.Optimized, 16: c.Hidden, } for v, b := range optionValues { if b { optionsBitmask = optionsBitmask | v } } return optionsBitmask } ================================================ FILE: plugin/table/column_test.go ================================================ package table import ( "encoding/json" "testing" "github.com/stretchr/testify/require" ) func TestColumnType_MarshalJSON(t *testing.T) { t.Parallel() tests := []struct { ct ColumnType expected string }{ {ColumnTypeUnknown, `"unknown"`}, {ColumnTypeText, `"text"`}, {ColumnTypeInteger, `"integer"`}, {ColumnTypeBigInt, `"bigint"`}, {ColumnTypeUnsignedBigInt, `"unsigned_bigint"`}, {ColumnTypeDouble, `"double"`}, {ColumnTypeBlob, `"blob"`}, {ColumnType("CUSTOM TYPE"), `"custom_type"`}, } for _, tt := range tests { got, err := json.Marshal(tt.ct) require.NoError(t, err) require.Equal(t, tt.expected, string(got)) } } func TestColumnDefinition_Options(t *testing.T) { t.Parallel() // Option bitmask values from osquery column.h: Index=1, Required=2, Additional=4, Optimized=8, Hidden=16 tests := []struct { name string in []ColumnOpt expected uint8 }{ { name: "no options", in: []ColumnOpt{}, expected: 0, }, { name: "Index only", in: []ColumnOpt{IndexColumn()}, expected: 1, }, { name: "Required only", in: []ColumnOpt{RequiredColumn()}, expected: 2, }, { name: "Additional only", in: []ColumnOpt{AdditionalColumn()}, expected: 4, }, { name: "Optimized only", in: []ColumnOpt{OptimizedColumn()}, expected: 8, }, { name: "Hidden only", in: []ColumnOpt{HiddenColumn()}, expected: 16, }, { name: "Index and Hidden", in: []ColumnOpt{IndexColumn(), HiddenColumn()}, expected: 17, }, { name: "all options", in: []ColumnOpt{IndexColumn(), RequiredColumn(), AdditionalColumn(), OptimizedColumn(), HiddenColumn()}, expected: 1 + 2 + 4 + 8 + 16, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() cd := TextColumn("foo", tt.in...) require.Equal(t, tt.expected, cd.Options()) }) } } func TestColumnOpts_set_fields(t *testing.T) { t.Parallel() t.Run("IndexColumn sets Index", func(t *testing.T) { t.Parallel() cd := TextColumn("c", IndexColumn()) require.True(t, cd.Index) require.False(t, cd.Required) }) t.Run("RequiredColumn sets Required", func(t *testing.T) { t.Parallel() cd := TextColumn("c", RequiredColumn()) require.True(t, cd.Required) }) t.Run("AdditionalColumn sets Additional", func(t *testing.T) { t.Parallel() cd := TextColumn("c", AdditionalColumn()) require.True(t, cd.Additional) }) t.Run("OptimizedColumn sets Optimized", func(t *testing.T) { t.Parallel() cd := TextColumn("c", OptimizedColumn()) require.True(t, cd.Optimized) }) t.Run("HiddenColumn sets Hidden", func(t *testing.T) { t.Parallel() cd := TextColumn("c", HiddenColumn()) require.True(t, cd.Hidden) }) t.Run("ColumnDescription sets Description", func(t *testing.T) { t.Parallel() cd := TextColumn("c", ColumnDescription("human-readable note")) require.Equal(t, "human-readable note", cd.Description) }) t.Run("multiple opts apply in order", func(t *testing.T) { t.Parallel() cd := TextColumn("c", IndexColumn(), ColumnDescription("desc"), RequiredColumn()) require.True(t, cd.Index) require.True(t, cd.Required) require.Equal(t, "desc", cd.Description) }) } func TestColumn_helpers_produce_correct_type(t *testing.T) { t.Parallel() tests := []struct { name string column ColumnDefinition want ColumnType }{ {"TextColumn", TextColumn("x"), ColumnTypeText}, {"IntegerColumn", IntegerColumn("x"), ColumnTypeInteger}, {"BigIntColumn", BigIntColumn("x"), ColumnTypeBigInt}, {"DoubleColumn", DoubleColumn("x"), ColumnTypeDouble}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() require.Equal(t, tt.want, tt.column.Type) require.Equal(t, "x", tt.column.Name) }) } } func TestNewColumn_applies_opts(t *testing.T) { t.Parallel() cd := NewColumn("custom", ColumnTypeBlob, IndexColumn(), ColumnDescription("blob col")) require.Equal(t, "custom", cd.Name) require.Equal(t, ColumnType("BLOB"), cd.Type) require.True(t, cd.Index) require.Equal(t, "blob col", cd.Description) } ================================================ FILE: plugin/table/spec.go ================================================ package table // OsqueryTableSpec is a struct compatible with the osquery spec files. It // can be marshalled to json if desired. type OsqueryTableSpec struct { Name string `json:"name"` Description string `json:"description"` Url string `json:"url"` Platforms []platformName `json:"platforms"` Evented bool `json:"evented"` Cacheable bool `json:"cacheable"` Notes string `json:"notes,omitempty"` Examples []string `json:"examples,omitempty"` Columns []ColumnDefinition `json:"columns"` } func (t *Plugin) Spec() OsqueryTableSpec { return OsqueryTableSpec{ Name: t.name, Description: t.description, Url: t.url, Platforms: t.platforms, Notes: t.notes, Examples: t.examples, Columns: t.columns, } } ================================================ FILE: plugin/table/spec_test.go ================================================ package table import ( "context" "encoding/json" "testing" "github.com/stretchr/testify/require" ) func TestTable_Spec(t *testing.T) { t.Parallel() mockGenerate := func(_ context.Context, _ QueryContext) ([]map[string]string, error) { return nil, nil } tests := []struct { name string plugin *Plugin expected string }{ { name: "single text column", plugin: NewPlugin("simple", []ColumnDefinition{TextColumn("simple_text")}, mockGenerate, WithPlatforms(DarwinPlatform), ), expected: `{ "name": "simple", "cacheable": false, "evented": false, "columns": [ { "name": "simple_text", "type": "TEXT", "index": false, "required": false, "additional": false, "optimized": false, "hidden": false } ], "description": "", "url": "", "platforms": ["darwin"] }`, }, { name: "multiple columns with mixed types", plugin: NewPlugin("mixed", []ColumnDefinition{ TextColumn("name"), IntegerColumn("pid"), BigIntColumn("size"), DoubleColumn("score"), }, mockGenerate, WithPlatforms(DarwinPlatform), ), expected: `{ "name": "mixed", "cacheable": false, "evented": false, "columns": [ { "name": "name", "type": "TEXT", "index": false, "required": false, "additional": false, "optimized": false, "hidden": false }, { "name": "pid", "type": "INTEGER", "index": false, "required": false, "additional": false, "optimized": false, "hidden": false }, { "name": "size", "type": "BIGINT", "index": false, "required": false, "additional": false, "optimized": false, "hidden": false }, { "name": "score", "type": "DOUBLE", "index": false, "required": false, "additional": false, "optimized": false, "hidden": false } ], "description": "", "url": "", "platforms": ["darwin"] }`, }, { name: "columns with options", plugin: NewPlugin("opts", []ColumnDefinition{ TextColumn("key", IndexColumn(), RequiredColumn()), IntegerColumn("count", HiddenColumn()), }, mockGenerate, WithPlatforms(DarwinPlatform), ), expected: `{ "name": "opts", "cacheable": false, "evented": false, "columns": [ { "name": "key", "type": "TEXT", "index": true, "required": true, "additional": false, "optimized": false, "hidden": false }, { "name": "count", "type": "INTEGER", "index": false, "required": false, "additional": false, "optimized": false, "hidden": true } ], "description": "", "url": "", "platforms": ["darwin"] }`, }, { name: "plugin with description, url, notes, examples, platforms", plugin: NewPlugin("full", []ColumnDefinition{TextColumn("id")}, mockGenerate, WithDescription("Table description"), WithURL("https://osquery.io/schema"), WithNotes("Some notes"), WithExample("SELECT * FROM full"), WithPlatforms(DarwinPlatform, LinuxPlatform), ), expected: `{ "name": "full", "cacheable": false, "evented": false, "columns": [ { "name": "id", "type": "TEXT", "index": false, "required": false, "additional": false, "optimized": false, "hidden": false } ], "description": "Table description", "url": "https://osquery.io/schema", "notes": "Some notes", "examples": ["SELECT * FROM full"], "platforms": ["darwin", "linux"] }`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() generatedSpec := tt.plugin.Spec() var expectedSpec OsqueryTableSpec require.NoError(t, json.Unmarshal([]byte(tt.expected), &expectedSpec)) require.EqualValues(t, expectedSpec, generatedSpec, "spec for %s", tt.name) }) } } func TestTable_Spec_marshal_json_column_type_format(t *testing.T) { t.Parallel() // ColumnType should marshal to JSON as lowercase with underscores (e.g. "unsigned_bigint") plugin := NewPlugin("t", []ColumnDefinition{ TextColumn("a"), NewColumn("b", ColumnTypeUnsignedBigInt), }, func(_ context.Context, _ QueryContext) ([]map[string]string, error) { return nil, nil }) spec := plugin.Spec() data, err := json.Marshal(spec) require.NoError(t, err) var decoded struct { Columns []struct { Name string `json:"name"` Type string `json:"type"` } `json:"columns"` } require.NoError(t, json.Unmarshal(data, &decoded)) require.Len(t, decoded.Columns, 2) require.Equal(t, "text", decoded.Columns[0].Type) require.Equal(t, "unsigned_bigint", decoded.Columns[1].Type) } ================================================ FILE: plugin/table/table.go ================================================ // Package table creates an osquery table plugin. package table import ( "context" "encoding/json" "runtime" "strconv" "github.com/osquery/osquery-go/gen/osquery" "github.com/osquery/osquery-go/traces" "github.com/pkg/errors" "go.opentelemetry.io/otel/codes" ) // Generate returns the rows generated by the table. The ctx argument // should be checked for cancellation if the generation performs a // substantial amount of work. The queryContext argument provides the // deserialized JSON query context from osquery. type GenerateFunc func(ctx context.Context, queryContext QueryContext) ([]map[string]string, error) type Plugin struct { name string columns []ColumnDefinition generate GenerateFunc description string url string notes string examples []string platforms []platformName } type TableOpt func(*Plugin) // WithDescription sets the informational table description that will be used if table specs are generated func WithDescription(description string) TableOpt { return func(tbl *Plugin) { tbl.description = description } } // WithURL sets the informational table URL that will be used if table specs are generated func WithURL(url string) TableOpt { return func(tbl *Plugin) { tbl.url = url } } // WithNotes sets the informational table notes that will be used if table specs are generated func WithNotes(notes string) TableOpt { return func(tbl *Plugin) { tbl.notes = notes } } // WithExample adds an informational example that will be used if table specs are generated. // Can be used more than once. func WithExample(example string) TableOpt { return func(tbl *Plugin) { tbl.examples = append(tbl.examples, example) } } // WithPlatform overrides the default of runtime.GOOS with a list of supported platforms. This // is used by the table spec generation. func WithPlatforms(platforms ...platformName) TableOpt { return func(tbl *Plugin) { tbl.platforms = platforms } } func NewPlugin(name string, columns []ColumnDefinition, gen GenerateFunc, opts ...TableOpt) *Plugin { tbl := &Plugin{ name: name, columns: columns, generate: gen, } for _, opt := range opts { opt(tbl) } // If the table platform isn't set, use runtime.GOOS to determine it if len(tbl.platforms) == 0 { switch runtime.GOOS { case "darwin": tbl.platforms = []platformName{DarwinPlatform} case "windows": tbl.platforms = []platformName{WindowsPlatform} case "linux": tbl.platforms = []platformName{LinuxPlatform} default: // Historic function signature has no error, so there's not much to do // for an unknown platform. } } return tbl } func (t *Plugin) Name() string { return t.name } func (t *Plugin) RegistryName() string { return "table" } func (t *Plugin) Routes() osquery.ExtensionPluginResponse { routes := []map[string]string{} for _, col := range t.columns { routes = append(routes, map[string]string{ "id": "column", "name": col.Name, "type": string(col.Type), "op": strconv.FormatUint(uint64(col.Options()), 10), }) } return routes } func (t *Plugin) Call(ctx context.Context, request osquery.ExtensionPluginRequest) osquery.ExtensionResponse { ctx, span := traces.StartSpan(ctx, t.name, "action", request["action"], "table_name", t.name) defer span.End() ok := osquery.ExtensionStatus{Code: 0, Message: "OK"} switch request["action"] { case "generate": queryContext, err := parseQueryContext(request["context"]) if err != nil { return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "error parsing context JSON: " + err.Error(), }, } } rows, err := t.generate(ctx, *queryContext) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "error generating table: " + err.Error(), }, } } return osquery.ExtensionResponse{ Status: &ok, Response: rows, } case "columns": return osquery.ExtensionResponse{ Status: &ok, Response: t.Routes(), } default: return osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "unknown action: " + request["action"], }, } } } func (t *Plugin) Ping() osquery.ExtensionStatus { return osquery.ExtensionStatus{Code: 0, Message: "OK"} } func (t *Plugin) Shutdown() {} // QueryContext contains the constraints from the WHERE clause of the query, // that can optionally be used to optimize the table generation. Note that the // osquery SQLite engine will perform the filtering with these constraints, so // it is not mandatory that they be used in table generation. type QueryContext struct { // Constraints is a map from column name to the details of the // constraints on that column. Constraints map[string]ConstraintList } // ConstraintList contains the details of the constraints for the given column. type ConstraintList struct { Affinity ColumnType Constraints []Constraint } // Constraint contains both an operator and an expression that are applied as // constraints in the query. type Constraint struct { Operator Operator Expression string } type platformName string const ( DarwinPlatform platformName = "darwin" WindowsPlatform platformName = "windows" LinuxPlatform platformName = "linux" ) // Operator is an enum of the osquery operators. type Operator int // The following operators are defined in osquery tables.h. const ( OperatorEquals Operator = 2 OperatorGreaterThan = 4 OperatorLessThanOrEquals = 8 OperatorLessThan = 16 OperatorGreaterThanOrEquals = 32 OperatorMatch = 64 OperatorLike = 65 OperatorGlob = 66 OperatorRegexp = 67 OperatorUnique = 1 OperatorIn = 3 ) // The following types and functions exist for parsing of the queryContext // JSON and are not made public. type queryContextJSON struct { Constraints []constraintListJSON `json:"constraints"` } type constraintListJSON struct { Name string `json:"name"` Affinity string `json:"affinity"` List json.RawMessage `json:"list"` } func parseQueryContext(ctxJSON string) (*QueryContext, error) { var parsed queryContextJSON err := json.Unmarshal([]byte(ctxJSON), &parsed) if err != nil { return nil, errors.Wrap(err, "unmarshaling context JSON") } ctx := QueryContext{map[string]ConstraintList{}} for _, cList := range parsed.Constraints { constraints, err := parseConstraintList(cList.List) if err != nil { return nil, err } ctx.Constraints[cList.Name] = ConstraintList{ Affinity: ColumnType(cList.Affinity), Constraints: constraints, } } return &ctx, nil } func parseConstraintList(constraints json.RawMessage) ([]Constraint, error) { var str string err := json.Unmarshal(constraints, &str) if err == nil { // string indicates empty list return []Constraint{}, nil } var cList []map[string]interface{} err = json.Unmarshal(constraints, &cList) if err != nil { // cannot do anything with other types return nil, errors.Errorf("unexpected context list: %s", string(constraints)) } cl := []Constraint{} for _, c := range cList { var op Operator switch opVal := c["op"].(type) { case string: // osquery < 3.0 with stringy types opInt, err := strconv.Atoi(opVal) if err != nil { return nil, errors.Errorf("parsing operator int: %s", c["op"]) } op = Operator(opInt) case float64: // osquery > 3.0 with strong types op = Operator(opVal) default: return nil, errors.Errorf("cannot parse type %T", opVal) } expr, ok := c["expr"].(string) if !ok { return nil, errors.Errorf("expr should be string: %s", c["expr"]) } cl = append(cl, Constraint{ Operator: op, Expression: expr, }) } return cl, nil } ================================================ FILE: plugin/table/table_test.go ================================================ package table import ( "context" "encoding/json" "errors" "testing" "github.com/osquery/osquery-go/gen/osquery" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestTablePlugin(t *testing.T) { var StatusOK = osquery.ExtensionStatus{Code: 0, Message: "OK"} var calledQueryCtx QueryContext plugin := NewPlugin( "mock", []ColumnDefinition{ TextColumn("text"), IntegerColumn("integer"), BigIntColumn("big_int"), DoubleColumn("double"), }, func(ctx context.Context, queryCtx QueryContext) ([]map[string]string, error) { calledQueryCtx = queryCtx return []map[string]string{ { "text": "hello world", "integer": "123", "big_int": "-1234567890", "double": "3.14159", }, }, nil }) // Basic methods assert.Equal(t, "table", plugin.RegistryName()) assert.Equal(t, "mock", plugin.Name()) assert.Equal(t, StatusOK, plugin.Ping()) assert.Equal(t, osquery.ExtensionPluginResponse{ {"id": "column", "name": "text", "type": "TEXT", "op": "0"}, {"id": "column", "name": "integer", "type": "INTEGER", "op": "0"}, {"id": "column", "name": "big_int", "type": "BIGINT", "op": "0"}, {"id": "column", "name": "double", "type": "DOUBLE", "op": "0"}, }, plugin.Routes()) // Call explicit columns action resp := plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "columns"}) assert.Equal(t, &StatusOK, resp.Status) assert.Equal(t, osquery.ExtensionPluginResponse{ {"id": "column", "name": "text", "type": "TEXT", "op": "0"}, {"id": "column", "name": "integer", "type": "INTEGER", "op": "0"}, {"id": "column", "name": "big_int", "type": "BIGINT", "op": "0"}, {"id": "column", "name": "double", "type": "DOUBLE", "op": "0"}, }, resp.Response) // Call with good action and context resp = plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "generate", "context": "{}"}) assert.Equal(t, QueryContext{map[string]ConstraintList{}}, calledQueryCtx) assert.Equal(t, &StatusOK, resp.Status) assert.Equal(t, osquery.ExtensionPluginResponse{ { "text": "hello world", "integer": "123", "big_int": "-1234567890", "double": "3.14159", }, }, resp.Response) } func TestTablePluginErrors(t *testing.T) { var called bool plugin := NewPlugin( "mock", []ColumnDefinition{ TextColumn("text"), IntegerColumn("integer"), BigIntColumn("big_int"), DoubleColumn("double"), }, func(ctx context.Context, queryCtx QueryContext) ([]map[string]string, error) { called = true return nil, errors.New("foobar") }, ) // Call with bad actions assert.Equal(t, int32(1), plugin.Call(context.Background(), osquery.ExtensionPluginRequest{}).Status.Code) assert.False(t, called) assert.Equal(t, int32(1), plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "bad"}).Status.Code) assert.False(t, called) // Call with good action but generate fails assert.Equal(t, int32(1), plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "generate", "context": "{[]}"}).Status.Code) assert.False(t, called) // Call with good action but generate fails resp := plugin.Call(context.Background(), osquery.ExtensionPluginRequest{"action": "generate", "context": "{}"}) assert.True(t, called) assert.Equal(t, int32(1), resp.Status.Code) assert.Equal(t, "error generating table: foobar", resp.Status.Message) } func TestParseConstraintList(t *testing.T) { var testCases = []struct { json string constraints []Constraint shouldErr bool }{ { json: "bad", shouldErr: true, }, { json: `{"foo": "bar"}`, shouldErr: true, }, { json: `""`, constraints: []Constraint{}, }, { json: `[{"op":"2","expr":"foo"}]`, constraints: []Constraint{ Constraint{OperatorEquals, "foo"}, }, }, { json: `[{"op":"4","expr":"3"},{"op":"16","expr":"4"}]`, constraints: []Constraint{ Constraint{OperatorGreaterThan, "3"}, Constraint{OperatorLessThan, "4"}, }, }, } for _, tt := range testCases { t.Run("", func(t *testing.T) { constraints, err := parseConstraintList(json.RawMessage(tt.json)) if tt.shouldErr { assert.NotNil(t, err) } else { assert.Equal(t, tt.constraints, constraints) } }) } } func TestParseQueryContext(t *testing.T) { var testCases = []struct { json string context QueryContext shouldErr bool }{ { json: "", shouldErr: true, }, { json: ` { "constraints":[ { "name":"big_int", "list":"", "affinity":"BIGINT" }, { "name":"double", "list":"", "affinity":"DOUBLE" }, { "name":"integer", "list":"", "affinity":"INTEGER" }, { "name":"text", "list":[ { "op":"2", "expr":"foo" } ], "affinity":"TEXT" } ] }`, context: QueryContext{map[string]ConstraintList{ "big_int": ConstraintList{ColumnTypeBigInt, []Constraint{}}, "double": ConstraintList{ColumnTypeDouble, []Constraint{}}, "integer": ConstraintList{ColumnTypeInteger, []Constraint{}}, "text": ConstraintList{ColumnTypeText, []Constraint{{OperatorEquals, "foo"}}}, }}, }, { json: ` { "constraints":[ { "name":"big_int", "list":"", "affinity":"BIGINT" }, { "name":"double", "list":[ { "op":"32", "expr":"3.1" } ], "affinity":"DOUBLE" }, { "name":"integer", "list":"", "affinity":"INTEGER" }, { "name":"text", "list":[ { "op":"2", "expr":"foobar" } ], "affinity":"TEXT" } ] } `, context: QueryContext{map[string]ConstraintList{ "big_int": ConstraintList{ColumnTypeBigInt, []Constraint{}}, "double": ConstraintList{ColumnTypeDouble, []Constraint{{OperatorGreaterThanOrEquals, "3.1"}}}, "integer": ConstraintList{ColumnTypeInteger, []Constraint{}}, "text": ConstraintList{ColumnTypeText, []Constraint{{OperatorEquals, "foobar"}}}, }}, }, } for _, tt := range testCases { t.Run("", func(t *testing.T) { context, err := parseQueryContext(tt.json) if tt.shouldErr { assert.NotNil(t, err) } else { assert.Equal(t, &tt.context, context) } }) } } func TestParseVaryingQueryContexts(t *testing.T) { var testCases = []struct { json string expectedContext *QueryContext shouldErr bool }{ { // Stringy JSON from osquery version < 3 `{"constraints":[{"name":"domain","list":[{"op":"2","expr":"kolide.co"}],"affinity":"TEXT"},{"name":"email","list":"","affinity":"TEXT"}]}`, &QueryContext{ Constraints: map[string]ConstraintList{ "domain": ConstraintList{Affinity: "TEXT", Constraints: []Constraint{Constraint{Operator: OperatorEquals, Expression: "kolide.co"}}}, "email": ConstraintList{Affinity: "TEXT", Constraints: []Constraint{}}, }, }, false, }, { // Strongly typed JSON from osquery version > 3 `{"constraints":[{"name":"domain","list":[{"op":2,"expr":"kolide.co"}],"affinity":"TEXT"},{"name":"email","list":[],"affinity":"TEXT"}]}`, &QueryContext{ Constraints: map[string]ConstraintList{ "domain": ConstraintList{Affinity: "TEXT", Constraints: []Constraint{Constraint{Operator: OperatorEquals, Expression: "kolide.co"}}}, "email": ConstraintList{Affinity: "TEXT", Constraints: []Constraint{}}, }, }, false, }, { // Stringy `{"constraints":[{"name":"path","list":[{"op":"65","expr":"%foobar"}],"affinity":"TEXT"},{"name":"query","list":[{"op":"2","expr":"kMDItemFSName = \"google*\""}],"affinity":"TEXT"}]}`, &QueryContext{ Constraints: map[string]ConstraintList{ "path": ConstraintList{Affinity: "TEXT", Constraints: []Constraint{Constraint{Operator: OperatorLike, Expression: "%foobar"}}}, "query": ConstraintList{Affinity: "TEXT", Constraints: []Constraint{Constraint{Operator: OperatorEquals, Expression: "kMDItemFSName = \"google*\""}}}, }, }, false, }, { // Strong `{"constraints":[{"name":"path","list":[{"op":65,"expr":"%foobar"}],"affinity":"TEXT"},{"name":"query","list":[{"op":2,"expr":"kMDItemFSName = \"google*\""}],"affinity":"TEXT"}]}`, &QueryContext{ Constraints: map[string]ConstraintList{ "path": ConstraintList{Affinity: "TEXT", Constraints: []Constraint{Constraint{Operator: OperatorLike, Expression: "%foobar"}}}, "query": ConstraintList{Affinity: "TEXT", Constraints: []Constraint{Constraint{Operator: OperatorEquals, Expression: "kMDItemFSName = \"google*\""}}}, }, }, false, }, // Error cases {`{bad json}`, nil, true}, {`{"constraints":[{"name":"foo","list":["bar", "baz"],"affinity":"TEXT"}]`, nil, true}, } for _, tt := range testCases { t.Run("", func(t *testing.T) { context, err := parseQueryContext(tt.json) if tt.shouldErr { require.Error(t, err) return } require.NoError(t, err) assert.Equal(t, tt.expectedContext, context) }) } } ================================================ FILE: server.go ================================================ package osquery import ( "context" "fmt" "sync" "time" "github.com/apache/thrift/lib/go/thrift" "github.com/osquery/osquery-go/gen/osquery" "github.com/osquery/osquery-go/traces" "github.com/osquery/osquery-go/transport" "github.com/pkg/errors" ) type OsqueryPlugin interface { // Name is the name used to refer to the plugin (eg. the name of the // table the plugin implements). Name() string // RegistryName is which "registry" the plugin should be added to. // Valid names are ["config", "logger", "table"]. RegistryName() string // Routes returns the detailed information about the interface exposed // by the plugin. See the example plugins for samples. Routes() osquery.ExtensionPluginResponse // Ping implements a health check for the plugin. If the plugin is in a // healthy state, StatusOK should be returned. Ping() osquery.ExtensionStatus // Call requests the plugin to perform its defined behavior, returning // a response containing the result. Call(context.Context, osquery.ExtensionPluginRequest) osquery.ExtensionResponse // Shutdown alerts the plugin to stop. Shutdown() } type ExtensionManager interface { Close() Ping() (*osquery.ExtensionStatus, error) Call(registry, item string, req osquery.ExtensionPluginRequest) (*osquery.ExtensionResponse, error) Extensions() (osquery.InternalExtensionList, error) RegisterExtension(info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) DeregisterExtension(uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) Options() (osquery.InternalOptionList, error) Query(sql string) (*osquery.ExtensionResponse, error) GetQueryColumns(sql string) (*osquery.ExtensionResponse, error) } const defaultTimeout = 1 * time.Second const defaultPingInterval = 5 * time.Second // ExtensionManagerServer is an implementation of the full ExtensionManager // API. Plugins can register with an extension manager, which handles the // communication with the osquery process. type ExtensionManagerServer struct { name string version string sockPath string serverClient ExtensionManager serverClientShouldShutdown bool // Whether to shutdown the client during server shutdown registry map[string](map[string]OsqueryPlugin) server thrift.TServer transport thrift.TServerTransport timeout time.Duration pingInterval time.Duration // How often to ping osquery server mutex sync.Mutex uuid osquery.ExtensionRouteUUID started bool // Used to ensure tests wait until the server is actually started } // validRegistryNames contains the allowable RegistryName() values. If a plugin // attempts to register with another value, the program will panic. var validRegistryNames = map[string]bool{ "table": true, "logger": true, "config": true, "distributed": true, } type ServerOption func(*ExtensionManagerServer) func ExtensionVersion(version string) ServerOption { return func(s *ExtensionManagerServer) { s.version = version } } func ServerTimeout(timeout time.Duration) ServerOption { return func(s *ExtensionManagerServer) { s.timeout = timeout } } func ServerPingInterval(interval time.Duration) ServerOption { return func(s *ExtensionManagerServer) { s.pingInterval = interval } } // ServerSideConnectivityCheckInterval Sets a thrift package variable for the ticker // interval used by connectivity check in thrift compiled TProcessorFunc implementations. // See the thrift docs for more information func ServerConnectivityCheckInterval(interval time.Duration) ServerOption { return func(s *ExtensionManagerServer) { s.mutex.Lock() defer s.mutex.Unlock() thrift.ServerConnectivityCheckInterval = interval } } // WithClient sets the server to use an existing ExtensionManagerClient // instead of creating a new one. func WithClient(client ExtensionManager) ServerOption { return func(s *ExtensionManagerServer) { s.serverClient = client } } // MaxSocketPathCharacters is set to 97 because a ".12345" uuid is added to the socket down stream // if the provided socket is greater than 97 we may exceed the limit of 103 (104 causes an error) // why 103 limit? https://unix.stackexchange.com/questions/367008/why-is-socket-path-length-limited-to-a-hundred-chars const MaxSocketPathCharacters = 97 // NewExtensionManagerServer creates a new extension management server // communicating with osquery over the socket at the provided path. If // resolving the address or connecting to the socket fails, this function will // error. func NewExtensionManagerServer(name string, sockPath string, opts ...ServerOption) (*ExtensionManagerServer, error) { if len(sockPath) > MaxSocketPathCharacters { return nil, errors.Errorf("socket path %s (%d characters) exceeded the maximum socket path character length of %d", sockPath, len(sockPath), MaxSocketPathCharacters) } // Initialize nested registry maps registry := make(map[string](map[string]OsqueryPlugin)) for reg := range validRegistryNames { registry[reg] = make(map[string]OsqueryPlugin) } manager := &ExtensionManagerServer{ name: name, sockPath: sockPath, registry: registry, timeout: defaultTimeout, pingInterval: defaultPingInterval, } for _, opt := range opts { opt(manager) } if manager.serverClient == nil { serverClient, err := NewClient(sockPath, manager.timeout) if err != nil { if serverClient != nil { serverClient.Close() } return nil, err } manager.serverClient = serverClient manager.serverClientShouldShutdown = true } return manager, nil } // RegisterPlugin adds one or more OsqueryPlugins to this extension manager. func (s *ExtensionManagerServer) RegisterPlugin(plugins ...OsqueryPlugin) { s.mutex.Lock() defer s.mutex.Unlock() for _, plugin := range plugins { if !validRegistryNames[plugin.RegistryName()] { panic("invalid registry name: " + plugin.RegistryName()) } s.registry[plugin.RegistryName()][plugin.Name()] = plugin } } func (s *ExtensionManagerServer) genRegistry() osquery.ExtensionRegistry { registry := osquery.ExtensionRegistry{} for regName := range s.registry { registry[regName] = osquery.ExtensionRouteTable{} for _, plugin := range s.registry[regName] { registry[regName][plugin.Name()] = plugin.Routes() } } return registry } // Start registers the extension plugins and begins listening on a unix socket // for requests from the osquery process. All plugins should be registered with // RegisterPlugin() before calling Start(). func (s *ExtensionManagerServer) Start() error { var server thrift.TServer err := func() error { s.mutex.Lock() defer s.mutex.Unlock() // check after the lock the serverClient is present. It could have gone away on very short restart loops if s.serverClient == nil { return errors.New("cannot start, shutdown in progress") } registry := s.genRegistry() stat, err := s.serverClient.RegisterExtension( &osquery.InternalExtensionInfo{ Name: s.name, Version: s.version, }, registry, ) if err != nil { return errors.Wrap(err, "registering extension") } if stat.Code != 0 { return errors.Errorf("status %d registering extension: %s", stat.Code, stat.Message) } s.uuid = stat.UUID listenPath := fmt.Sprintf("%s.%d", s.sockPath, stat.UUID) processor := osquery.NewExtensionProcessor(s) s.transport, err = transport.OpenServer(listenPath, s.timeout) if err != nil { openError := errors.Wrapf(err, "opening server socket (%s)", listenPath) _, err = s.serverClient.DeregisterExtension(stat.UUID) if err != nil { return errors.Wrapf(err, "deregistering extension - follows %s", openError.Error()) } return openError } s.server = thrift.NewTSimpleServer2(processor, s.transport) server = s.server s.started = true return nil }() if err != nil { return err } return server.Serve() } // Run starts the extension manager and runs until osquery calls for a shutdown // or the osquery instance goes away. func (s *ExtensionManagerServer) Run() error { errc := make(chan error) go func() { errc <- s.Start() }() // Watch for the osquery process going away. If so, initiate shutdown. go func() { for { time.Sleep(s.pingInterval) s.mutex.Lock() serverClient := s.serverClient s.mutex.Unlock() // can't ping if s.Shutdown has already happened if serverClient == nil { break } status, err := serverClient.Ping() if err != nil { errc <- errors.Wrap(err, "extension ping failed") break } if status.Code != 0 { errc <- errors.Errorf("ping returned status %d", status.Code) break } } }() err := <-errc _ = s.Shutdown(context.Background()) return err } // Ping implements the basic health check. func (s *ExtensionManagerServer) Ping(ctx context.Context) (*osquery.ExtensionStatus, error) { return &osquery.ExtensionStatus{Code: 0, Message: "OK"}, nil } // Call routes a call from the osquery process to the appropriate registered // plugin. func (s *ExtensionManagerServer) Call(ctx context.Context, registry string, item string, request osquery.ExtensionPluginRequest) (*osquery.ExtensionResponse, error) { ctx, span := traces.StartSpan(ctx, "ExtensionManagerServer.Call", "registry", registry, "item", item, ) defer span.End() subreg, ok := s.registry[registry] if !ok { return &osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "Unknown registry: " + registry, }, }, nil } plugin, ok := subreg[item] if !ok { return &osquery.ExtensionResponse{ Status: &osquery.ExtensionStatus{ Code: 1, Message: "Unknown registry item: " + item, }, }, nil } response := plugin.Call(ctx, request) return &response, nil } // Shutdown deregisters the extension, stops the server and closes all sockets. func (s *ExtensionManagerServer) Shutdown(ctx context.Context) (err error) { s.mutex.Lock() defer s.mutex.Unlock() if s.serverClient != nil { var stat *osquery.ExtensionStatus stat, err = s.serverClient.DeregisterExtension(s.uuid) err = errors.Wrap(err, "deregistering extension") if err == nil && stat.Code != 0 { err = errors.Errorf("status %d deregistering extension: %s", stat.Code, stat.Message) } } if s.server != nil { server := s.server s.server = nil // Stop the server asynchronously so that the current request // can complete. Otherwise, this is vulnerable to deadlock if a // shutdown request is being processed when Shutdown is // explicitly called. go func() { server.Stop() }() } // Shutdown the client, if appropriate if s.serverClientShouldShutdown && s.serverClient != nil { s.serverClient.Close() s.serverClient = nil } return } // Useful for testing func (s *ExtensionManagerServer) waitStarted() { for { s.mutex.Lock() started := s.started s.mutex.Unlock() if started { time.Sleep(10 * time.Millisecond) break } } } ================================================ FILE: server_test.go ================================================ package osquery import ( "context" "errors" "fmt" "io/ioutil" "net" "os" "runtime/pprof" "strings" "sync" "syscall" "testing" "time" "github.com/apache/thrift/lib/go/thrift" "github.com/osquery/osquery-go/gen/osquery" "github.com/osquery/osquery-go/plugin/logger" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Verify that an error in server.Start will return an error instead of deadlock. func TestNoDeadlockOnError(t *testing.T) { registry := make(map[string](map[string]OsqueryPlugin)) for reg := range validRegistryNames { registry[reg] = make(map[string]OsqueryPlugin) } mut := sync.Mutex{} mock := &MockExtensionManager{ RegisterExtensionFunc: func(info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) { mut.Lock() defer mut.Unlock() return nil, errors.New("boom!") }, PingFunc: func() (*osquery.ExtensionStatus, error) { return &osquery.ExtensionStatus{}, nil }, DeRegisterExtensionFunc: func(uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) { return &osquery.ExtensionStatus{}, nil }, CloseFunc: func() {}, } server := &ExtensionManagerServer{ serverClient: mock, registry: registry, serverClientShouldShutdown: true, } log := func(ctx context.Context, typ logger.LogType, logText string) error { fmt.Printf("%s: %s\n", typ, logText) return nil } server.RegisterPlugin(logger.NewPlugin("testLogger", log)) err := server.Run() assert.Error(t, err) mut.Lock() defer mut.Unlock() assert.True(t, mock.RegisterExtensionFuncInvoked) } // Ensure that the extension server will shutdown and return if the osquery // instance it is talking to stops responding to pings. func TestShutdownWhenPingFails(t *testing.T) { tempPath, err := ioutil.TempFile("", "") require.Nil(t, err) defer os.Remove(tempPath.Name()) registry := make(map[string](map[string]OsqueryPlugin)) for reg := range validRegistryNames { registry[reg] = make(map[string]OsqueryPlugin) } mock := &MockExtensionManager{ RegisterExtensionFunc: func(info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) { return &osquery.ExtensionStatus{}, nil }, PingFunc: func() (*osquery.ExtensionStatus, error) { // As if the socket was closed return nil, syscall.EPIPE }, DeRegisterExtensionFunc: func(uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) { return &osquery.ExtensionStatus{}, nil }, CloseFunc: func() {}, } server := &ExtensionManagerServer{ serverClient: mock, registry: registry, serverClientShouldShutdown: true, pingInterval: 1 * time.Second, sockPath: tempPath.Name(), } err = server.Run() assert.Error(t, err) assert.Contains(t, err.Error(), "broken pipe") assert.True(t, mock.DeRegisterExtensionFuncInvoked) assert.True(t, mock.CloseFuncInvoked) } // How many parallel tests to run (because sync issues do not occur on every // run, this maximizes our chances of seeing any issue by quickly executing // many runs of the test). const parallelTestShutdownDeadlock = 20 func TestShutdownDeadlock(t *testing.T) { for i := 0; i < parallelTestShutdownDeadlock; i++ { i := i t.Run("", func(t *testing.T) { t.Parallel() testShutdownDeadlock(t, i) }) } } func testShutdownDeadlock(t *testing.T, uuid int) { tempPath, err := ioutil.TempFile("", "") require.Nil(t, err) defer os.Remove(tempPath.Name()) retUUID := osquery.ExtensionRouteUUID(uuid) mock := &MockExtensionManager{ RegisterExtensionFunc: func(info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) { return &osquery.ExtensionStatus{Code: 0, UUID: retUUID}, nil }, DeRegisterExtensionFunc: func(uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) { return &osquery.ExtensionStatus{}, nil }, CloseFunc: func() {}, } server := ExtensionManagerServer{ serverClient: mock, sockPath: tempPath.Name(), timeout: defaultTimeout, serverClientShouldShutdown: true, } var wait sync.WaitGroup go func() { // We do not wait for this routine to finish because thrift.TServer.Serve // seems to sometimes hang after shutdowns. (This test is just testing // the Shutdown doesn't hang.) err := server.Start() require.NoError(t, err) }() // Wait for server to be set up server.waitStarted() // Create a raw client to access the shutdown method that is not // usually exposed. listenPath := fmt.Sprintf("%s.%d", tempPath.Name(), retUUID) addr, err := net.ResolveUnixAddr("unix", listenPath) require.Nil(t, err) timeout := 500 * time.Millisecond opened := false attempt := 0 var transport *thrift.TSocket for !opened && attempt < 10 { transport = thrift.NewTSocketFromAddrTimeout(addr, timeout, timeout) err = transport.Open() attempt++ if err != nil { time.Sleep(1 * time.Second) } else { opened = true } } require.NoError(t, err) client := osquery.NewExtensionManagerClientFactory(transport, thrift.NewTBinaryProtocolFactoryDefault()) // Simultaneously call shutdown through a request from the client and // directly on the server object. wait.Add(1) go func() { defer wait.Done() client.Shutdown(context.Background()) }() wait.Add(1) go func() { defer wait.Done() err = server.Shutdown(context.Background()) require.NoError(t, err) }() // Track whether shutdown completed completed := make(chan struct{}) go func() { wait.Wait() close(completed) }() // either indicate successful shutdown, or fatal the test because it // hung select { case <-completed: // Success. Do nothing. case <-time.After(10 * time.Second): pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) t.Fatal("hung on shutdown") } } func TestShutdownBasic(t *testing.T) { dir := t.TempDir() tempPath := func() string { tmp, err := os.CreateTemp(dir, "") require.NoError(t, err) return tmp.Name() } retUUID := osquery.ExtensionRouteUUID(0) mock := &MockExtensionManager{ RegisterExtensionFunc: func(info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) { return &osquery.ExtensionStatus{Code: 0, UUID: retUUID}, nil }, DeRegisterExtensionFunc: func(uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) { return &osquery.ExtensionStatus{}, nil }, CloseFunc: func() {}, } for _, server := range []*ExtensionManagerServer{ // Create the extension manager without using NewExtensionManagerServer. {serverClient: mock, sockPath: tempPath()}, // Create the extension manager using ExtensionManagerServer. {serverClient: mock, sockPath: tempPath(), serverClientShouldShutdown: true}, } { completed := make(chan struct{}) go func() { err := server.Start() require.NoError(t, err) close(completed) }() server.waitStarted() err := server.Shutdown(context.Background()) require.NoError(t, err) // Test that server.Shutdown is idempotent. err = server.Shutdown(context.Background()) require.NoError(t, err) // Either indicate successful shutdown, or fatal the test because it // hung select { case <-completed: // Success. Do nothing. case <-time.After(5 * time.Second): t.Fatal("hung on shutdown") } } } func TestNewExtensionManagerServer(t *testing.T) { t.Parallel() type args struct { name string sockPath string opts []ServerOption } tests := []struct { name string args args want *ExtensionManagerServer errContainsStr string }{ { name: "socket path too long", args: args{ name: "socket_path_too_long", sockPath: strings.Repeat("a", MaxSocketPathCharacters+1), opts: []ServerOption{}, }, errContainsStr: "exceeded the maximum socket path character length", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := NewExtensionManagerServer(tt.args.name, tt.args.sockPath, tt.args.opts...) if tt.errContainsStr != "" { require.Error(t, err) require.Contains(t, err.Error(), tt.errContainsStr) } else { require.NoError(t, err) require.NotNil(t, got) } }) } } ================================================ FILE: traces/traces.go ================================================ // Package traces allows for instrumenting osquery-go with OpenTelemetry traces. // Unless the consuming application specifically configures a trace exporter, all tracing is a no-op. package traces import ( "context" "fmt" "runtime/debug" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) const instrumentationPkg = "github.com/osquery/osquery-go" var ( internalVersion string // provides the instrumentation version for attribute `otel.scope.version` tracerProvider trace.TracerProvider ) // init sets `internalVersion` and a default tracer provider. func init() { // By default, use the global tracer provider, which is a no-op provider. tracerProvider = otel.GetTracerProvider() // Look through build info to determine the current version of the osquery-go package. if info, ok := debug.ReadBuildInfo(); ok { for _, dep := range info.Deps { if dep == nil { continue } if dep.Path == instrumentationPkg { internalVersion = dep.Version return } } } // Couldn't get the version from runtime build info -- save 0.0.0, // which is the current osquery-go version. internalVersion = "0.0.0" } // SetTracerProvider allows consuming libraries to set a custom/non-global tracer provider. func SetTracerProvider(tp trace.TracerProvider) { tracerProvider = tp } // OsqueryGoTracer provides a tracer with a standardized name and version. // It should be used to start a span that requires `SpanStartOption`s that are // not supported by `StartSpan` below -- i.e., any `SpanStartOption` besides // `WithAttributes`. func OsqueryGoTracer() trace.Tracer { return tracerProvider.Tracer(instrumentationPkg, trace.WithInstrumentationVersion(internalVersion)) } // StartSpan is a wrapper around trace.Tracer.Start that simplifies passing in span attributes. // `keyVals` should be a list of pairs, where the first in the pair is a string representing // the attribute key and the second in the pair is the attribute value. // The caller is always responsible for ending the returned span. // Any spans requiring more specific configuration can be created manually via OsqueryGoTracer().Start. func StartSpan(ctx context.Context, spanName string, keyVals ...string) (context.Context, trace.Span) { attrs := make([]attribute.KeyValue, 0) for i := 0; i < len(keyVals); i += 2 { // Ensure all attributes are appropriately namespaced key := fmt.Sprintf("osquery-go.%s", keyVals[i]) attrs = append(attrs, attribute.String(key, keyVals[i+1])) } opts := []trace.SpanStartOption{trace.WithAttributes(attrs...)} return OsqueryGoTracer().Start(ctx, spanName, opts...) } ================================================ FILE: traces/traces_test.go ================================================ package traces import ( "context" "sync" "testing" "github.com/stretchr/testify/assert" ) func TestTraceInit(t *testing.T) { t.Parallel() // Start several spans in quick succession to confirm there's no data race on setting `internalVersion` var wg sync.WaitGroup for i := 0; i < 5; i += 1 { wg.Add(1) go func() { defer wg.Done() _, span := StartSpan(context.TODO(), "TestSpan") span.End() }() } wg.Wait() assert.NotEmpty(t, internalVersion, "internal version should have been set") } ================================================ FILE: transport/doc.go ================================================ // Package transport provides Thrift TTransport and TServerTransport // implementations for use on mac/linux (TSocket/TServerSocket) and Windows // (custom named pipe implementation). package transport ================================================ FILE: transport/transport.go ================================================ //go:build !windows // +build !windows package transport import ( "context" "net" "os" "time" "github.com/apache/thrift/lib/go/thrift" "github.com/pkg/errors" ) // Open opens the unix domain socket with the provided path and timeout, // returning a TTransport. func Open(sockPath string, timeout time.Duration) (*thrift.TSocket, error) { addr, err := net.ResolveUnixAddr("unix", sockPath) if err != nil { return nil, errors.Wrapf(err, "resolving socket path '%s'", sockPath) } // the timeout parameter is passed to thrift, which passes it to net.DialTimeout // but it looks like net.DialTimeout ignores timeouts for unix socket and immediately returns an error // waitForSocket will loop every 200ms to stat the socket path, // or until the timeout value passes, similar to the C++ and python implementations. if err := waitForSocket(sockPath, timeout); err != nil { return nil, errors.Wrapf(err, "waiting for unix socket to be available: %s", sockPath) } trans := thrift.NewTSocketFromAddrTimeout(addr, timeout, timeout) if err := trans.Open(); err != nil { return nil, errors.Wrap(err, "opening socket transport") } return trans, nil } func OpenServer(listenPath string, timeout time.Duration) (*thrift.TServerSocket, error) { addr, err := net.ResolveUnixAddr("unix", listenPath) if err != nil { return nil, errors.Wrapf(err, "resolving addr (%s)", addr) } return thrift.NewTServerSocketFromAddrTimeout(addr, 0), nil } func waitForSocket(sockPath string, timeout time.Duration) error { ticker := time.NewTicker(200 * time.Millisecond) defer ticker.Stop() ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() for { select { case <-ctx.Done(): return ctx.Err() case <-ticker.C: if _, err := os.Stat(sockPath); err == nil { return nil } } } } ================================================ FILE: transport/transport_windows.go ================================================ //go:build windows // +build windows package transport import ( "net" "sync" "time" "github.com/Microsoft/go-winio" "github.com/pkg/errors" "github.com/apache/thrift/lib/go/thrift" ) // Open opens the named pipe with the provided path and timeout, // returning a TTransport. func Open(path string, timeout time.Duration) (*thrift.TSocket, error) { conn, err := winio.DialPipe(path, &timeout) if err != nil { return nil, errors.Wrapf(err, "dialing pipe '%s'", path) } return thrift.NewTSocketFromConnTimeout(conn, timeout), nil } func OpenServer(pipePath string, timeout time.Duration) (*TServerPipe, error) { return NewTServerPipeTimeout(pipePath, timeout) } // TServerPipe is a windows named pipe implementation of the type TServerPipe struct { listener net.Listener pipePath string clientTimeout time.Duration // Protects the interrupted value to make it thread safe. mu sync.RWMutex interrupted bool } func NewTServerPipeTimeout(pipePath string, clientTimeout time.Duration) (*TServerPipe, error) { return &TServerPipe{pipePath: pipePath, clientTimeout: clientTimeout}, nil } func (p *TServerPipe) Listen() error { p.mu.Lock() defer p.mu.Unlock() if p.IsListening() { return nil } l, err := winio.ListenPipe(p.pipePath, nil) if err != nil { return err } p.listener = l return nil } // IsListening returns whether the server transport is currently listening. func (p *TServerPipe) IsListening() bool { return p.listener != nil } // Accept wraps the standard net.Listener accept to return a thrift.TTransport. func (p *TServerPipe) Accept() (thrift.TTransport, error) { p.mu.RLock() interrupted := p.interrupted listener := p.listener p.mu.RUnlock() if interrupted { return nil, errors.New("transport interrupted") } conn, err := listener.Accept() if err != nil { return nil, thrift.NewTTransportExceptionFromError(err) } return thrift.NewTSocketFromConnTimeout(conn, p.clientTimeout), nil } func (p *TServerPipe) Close() error { defer func() { p.listener = nil }() if p.IsListening() { return p.listener.Close() } return nil } // Interrupt is a noop for this implementation func (p *TServerPipe) Interrupt() error { p.mu.Lock() defer p.mu.Unlock() p.interrupted = true p.Close() return nil }