Merge pull request #1594 from owncloud/ocis-1643-migrate-ocis-graph

Add ocis-graph
This commit is contained in:
David Christofas
2021-02-16 10:43:43 +01:00
committed by GitHub
76 changed files with 3964 additions and 917 deletions

View File

@@ -13,7 +13,9 @@ config = {
'store':'',
'thumbnails':'',
'webdav':'',
'onlyoffice':'frontend'
'onlyoffice':'frontend',
'graph': '',
'graph-explorer': ''
},
'apiTests': {
'numberOfParts': 10

View File

@@ -1042,6 +1042,7 @@ github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE=
github.com/ory/x v0.0.162 h1:xE/UBmmMlInTvlgGXUyo+VeZAcWU5gyWb/xh6jmBWsI=
github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203 h1:sXVTQ94GWeiBhlgN0kSbcyXQNmwzPuuhWyObxgTu/xE=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203/go.mod h1:jip86t4OVURJTf8CM/0e2qcji/Y4NG3l2lR8kex4JWw=
github.com/owncloud/ocis/accounts v0.5.3-0.20201103104733-ff2c41028d9b/go.mod h1:IX7T4MJ1U8Y4z9dfDW1J2jq1nv/SHCM7n2zt1fmBHI8=
github.com/owncloud/ocis/storage v0.0.0-20201015120921-38358ba4d4df h1:PhRLD+WTGIfQ1T4MqBabp6/1Q8H/iwxjlygh6xzao0A=

View File

@@ -0,0 +1,9 @@
Change: Initial release of graph and graph explorer
Tags: graph, graph-explorer
We brought initial basic Graph and Graph-Explorer support for the ownCloud
Infinite Scale project.
https://github.com/owncloud/ocis/pull/1594
https://github.com/owncloud/ocis-graph-explorer/pull/3

View File

@@ -0,0 +1,7 @@
Enhancement: Migrate ocis-graph to ocis monorepo
Tags: ocis, ocis-graph
Ocis-graph was not migrated during the monorepo conversion.
https://github.com/owncloud/ocis/pull/1594

View File

@@ -0,0 +1 @@
configuration.md

View File

@@ -0,0 +1,11 @@
---
title: "Graph-Explorer"
date: 2018-05-02T00:00:00+00:00
weight: 20
geekdocRepo: https://github.com/owncloud/ocis
geekdocEditPath: edit/master/docs/extensions/graph-explorer
geekdocFilePath: _index.md
geekdocCollapseSection: true
---
This service embeds [Graph-Explorer](https://github.com/owncloud/ocis/graph-explorer) to provide a UI for ownCloud Infinite Scale.

1
docs/extensions/graph/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
configuration.md

View File

@@ -0,0 +1,11 @@
---
title: "Graph"
date: 2018-05-02T00:00:00+00:00
weight: 20
geekdocRepo: https://github.com/owncloud/ocis
geekdocEditPath: edit/master/docs/extensions/graph
geekdocFilePath: _index.md
geekdocCollapseSection: true
---
This service provides a simple graph world API which can be used by clients or other extensions.

View File

@@ -1120,6 +1120,7 @@ github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE=
github.com/ory/x v0.0.162 h1:xE/UBmmMlInTvlgGXUyo+VeZAcWU5gyWb/xh6jmBWsI=
github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203 h1:sXVTQ94GWeiBhlgN0kSbcyXQNmwzPuuhWyObxgTu/xE=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203/go.mod h1:jip86t4OVURJTf8CM/0e2qcji/Y4NG3l2lR8kex4JWw=
github.com/owncloud/ocis-pkg/v2 v2.4.0/go.mod h1:FSzIvhx9HcZcq4jgNaDowNvM7PTX/XCyoMvyfzidUpE=
github.com/owncloud/ocis-pkg/v2 v2.4.1-0.20200902134813-1e87c6173ada h1:iVknnuT/z8QCAeBpHEcbI/AiQ9FOBvF5aOAFT3TIM+I=

View File

@@ -58,7 +58,7 @@ sync:
.PHONY: clean
clean:
go clean -i ./...
rm -rf $(BIN) $(DIST)
rm -rf $(BIN) $(DIST) assets
.PHONY: go-mod-tidy
go-mod-tidy:
@@ -81,7 +81,7 @@ lint:
for PKG in $(PACKAGES); do go run golang.org/x/lint/golint -set_exit_status $$PKG || exit 1; done;
.PHONY: generate
generate:
generate: assets
go generate $(GENERATE)
.PHONY: changelog
@@ -186,3 +186,6 @@ watch:
# .PHONY: protobuf
# protobuf: $(GOPATH)/bin/protoc-gen-go $(GOPATH)/bin/protoc-gen-micro $(GOPATH)/bin/protoc-gen-microweb $(GOPATH)/bin/protoc-gen-swagger pkg/proto/v0/example.pb.go pkg/proto/v0/example.pb.micro.go pkg/proto/v0/example.pb.web.go pkg/proto/v0/example.swagger.json
assets:
mkdir assets/ && curl -slL -o- https://github.com/owncloud/graph-explorer/releases/download/v1.7.10.3/release.tar.gz | tar xvzf - -C assets/

View File

@@ -1,28 +0,0 @@
{{- range $changes := . }}{{ with $changes -}}
# Changelog for {{ .Version }}
The following sections list the changes for {{ .Version }}.
## Summary
{{ range $entry := .Entries }}{{ with $entry }}
* {{ .TypeShort }} #{{ .PrimaryID }}: {{ .Title }}
{{- end }}{{ end }}
## Details
{{ range $entry := .Entries }}{{ with $entry }}
* {{ .Type }} #{{ .PrimaryID }}: {{ .Title }}
{{ range $par := .Paragraphs }}
{{ wrap $par 80 3 }}
{{ end -}}
{{ range $url := .IssueURLs }}
{{ $url -}}
{{ end -}}
{{ range $url := .PRURLs }}
{{ $url -}}
{{ end -}}
{{ range $url := .OtherURLs }}
{{ $url -}}
{{ end }}
{{ end }}{{ end }}
{{ end }}{{ end -}}

View File

@@ -1,6 +0,0 @@
# Changelog
We are using [calens](https://github.com/restic/calens) to properly generate a
changelog before we are tagging a new release. To get an idea how this could
look like <https://github.com/restic/restic/tree/master/changelog> would be the
best reference.

View File

@@ -1,11 +0,0 @@
Bugfix: Fix behavior for foobar (in present tense)
We've fixed the behavior for foobar, a long-standing annoyance for users. The
text should be wrapped at 80 characters length.
The text in the paragraphs is written in past tense. The last section is a list
of issue URLs, PR URLs and other URLs. The first issue ID (or the first PR ID,
in case there aren't any issue links) is used as the primary ID.
https://github.com/owncloud/ocis-graph-explorer/issues/1234
https://github.com/owncloud/ocis-graph-explorer/pull/55555

View File

@@ -1,7 +0,0 @@
Change: Initial release of basic version
Just prepared an initial basic version to serve Graph-Explorer for the ownCloud
Infinite Scale project. It just provides a minimal viable product to
demonstrate the microservice pattern.
https://github.com/owncloud/ocis-graph-explorer/issues/3

View File

@@ -1,5 +0,0 @@
Bugfix: build docker images with alpine:latest instead of alpine:edge
ARM builds were failing when built on alpine:edge, so we switched to alpine:latest instead.
https://github.com/owncloud/ocis-graph-explorer/pull/7

View File

@@ -1 +0,0 @@
public/

View File

@@ -1,6 +0,0 @@
---
title: "{{ replace .TranslationBaseName "-" " " | title }}"
date: {{ .Date }}
anchor: "{{ replace .TranslationBaseName "-" " " | title | urlize }}"
weight:
---

View File

@@ -1,18 +0,0 @@
baseURL = "https://owncloud.github.io/ocis-graph-explorer/"
languageCode = "en-us"
title = "ownCloud Infinite Scale: Graph-Explorer"
pygmentsUseClasses = true
disableKinds = ["taxonomy", "taxonomyTerm", "RSS", "sitemap"]
[blackfriday]
angledQuotes = true
fractions = false
plainIDAnchors = true
smartlists = true
extensions = ["hardLineBreak"]
[params]
author = "ownCloud GmbH"
description = "Serve Graph-Explorer for oCIS"
keywords = "reva, ocis"

View File

@@ -1,8 +0,0 @@
---
title: "About"
date: 2018-05-02T00:00:00+00:00
anchor: "about"
weight: 10
---
This service embeds [Graph-Explorer](https://github.com/owncloud/graph-explorer) to provide a UI for ownCloud Infinite Scale.

View File

@@ -1,24 +0,0 @@
---
title: "Building"
date: 2018-05-02T00:00:00+00:00
anchor: "building"
weight: 30
---
As this project is built with Go, so you need to install that first. The installation of Go is out of the scope of this document, please follow the official documentation for [Go](https://golang.org/doc/install), to build this project you have to install Go >= v1.12. After the installation of the required tools you need to get the sources:
{{< highlight txt >}}
git clone https://github.com/owncloud/ocis-graph-explorer.git
cd ocis-graph-explorer
{{< / highlight >}}
All required tool besides Go itself and make are bundled or getting automatically installed within the `GOPATH`. All commands to build this project are part of our `Makefile`.
### Backend
{{< highlight txt >}}
make generate
make build
{{< / highlight >}}
The above commands will download a [Graph-Explorer](https://github.com/owncloud/graph-explorer) release and embed it into the binary. Finally you should have the binary within the `bin/` folder now, give it a try with `./bin/ocis-graph-explorer -h` to see all available options.

View File

@@ -1,335 +0,0 @@
---
title: "Getting Started"
date: 2018-05-02T00:00:00+00:00
anchor: "getting-started"
weight: 20
---
### Installation
So far we are offering two different variants for the installation. You can choose between [Docker](https://www.docker.com/) or pre-built binaries which are stored on our download mirrors and GitHub releases. Maybe we will also provide system packages for the major distributions later if we see the need for it.
#### Docker
TBD
#### Binaries
TBD
### Configuration
We provide overall three different variants of configuration. The variant based on environment variables and commandline flags are split up into global values and command-specific values.
#### Envrionment variables
If you prefer to configure the service with environment variables you can see the available variables below.
##### Global
GRAPH_EXPLORER_CONFIG_FILE
: Path to config file, empty default value
GRAPH_EXPLORER_LOG_LEVEL
: Set logging level, defaults to `info`
GRAPH_EXPLORER_LOG_COLOR
: Enable colored logging, defaults to `true`
GRAPH_EXPLORER_LOG_PRETTY
: Enable pretty logging, defaults to `true`
##### Server
GRAPH_EXPLORER_TRACING_ENABLED
: Enable sending traces, defaults to `false`
GRAPH_EXPLORER_TRACING_TYPE
: Tracing backend type, defaults to `jaeger`
GRAPH_EXPLORER_TRACING_ENDPOINT
: Endpoint for the agent, empty default value
GRAPH_EXPLORER_TRACING_COLLECTOR
: Endpoint for the collector, empty default value
GRAPH_EXPLORER_TRACING_SERVICE
: Service name for tracing, defaults to `graph-explorer`
GRAPH_EXPLORER_DEBUG_ADDR
: Address to bind debug server, defaults to `0.0.0.0:9104`
GRAPH_EXPLORER_DEBUG_TOKEN
: Token to grant metrics access, empty default value
GRAPH_EXPLORER_DEBUG_PPROF
: Enable pprof debugging, defaults to `false`
GRAPH_EXPLORER_DEBUG_ZPAGES
: Enable zpages debugging, defaults to `false`
GRAPH_EXPLORER_HTTP_ADDR
: Address to bind http server, defaults to `0.0.0.0:9100`
GRAPH_EXPLORER_HTTP_ROOT
: Root path of http server, defaults to `/`
GRAPH_EXPLORER_ASSET_PATH
: Path to custom assets, empty default value
GRAPH_EXPLORER_WEB_CONFIG
: Path to graph-explorer config, empty default value
GRAPH_EXPLORER_WEB_CONFIG_SERVER
: Server URL, defaults to `http://localhost:9135`
GRAPH_EXPLORER_WEB_CONFIG_THEME
: Theme, defaults to `owncloud`
GRAPH_EXPLORER_WEB_CONFIG_VERSION
: Version, defaults to `0.1.0`
GRAPH_EXPLORER_APPS
: Use multiple times to provide multiple apps
GRAPH_EXPLORER_EXTERNAL_APPS
: Not supported yet, specify a config.json file via `GRAPH_EXPLORER_WEB_CONFIG`
GRAPH_EXPLORER_OIDC_METADATA_URL
: OpenID Connect metadata URL, defaults to `http://localhost:9130/.well-known/openid-configuration`
GRAPH_EXPLORER_OIDC_AUTHORITY
: OpenID Connect authority, defaults to `http://localhost:9130`
GRAPH_EXPLORER_OIDC_CLIENT_ID
: OpenID Connect client ID, defaults to `graph-explorer`
GRAPH_EXPLORER_OIDC_RESPONSE_TYPE
: OpenID Connect response type, defaults to `code`
GRAPH_EXPLORER_OIDC_SCOPE
: OpenID Connect scope, defaults to `openid profile email`
In case you want to render any additional properties in the config.json you can provide a custom config.json using eg. `GRAPH_EXPLORER_WEB_CONFIG=/path/to/config.json ocis-graph-explorer server`
##### Health
GRAPH_EXPLORER_DEBUG_ADDR
: Address to debug endpoint, defaults to `0.0.0.0:9104`
#### Commandline flags
If you prefer to configure the service with commandline flags you can see the available variables below.
##### Global
--config-file
: Path to config file, empty default value
--log-level
: Set logging level, defaults to `info`
--log-color
: Enable colored logging, defaults to `true`
--log-pretty
: Enable pretty logging, defaults to `true`
##### Server
--tracing-enabled
: Enable sending traces, defaults to `false`
--tracing-type
: Tracing backend type, defaults to `jaeger`
--tracing-endpoint
: Endpoint for the agent, empty default value
--tracing-collector
: Endpoint for the collector, empty default value
--tracing-service
: Service name for tracing, defaults to `graph-explorer`
--debug-addr
: Address to bind debug server, defaults to `0.0.0.0:9104`
--debug-token
: Token to grant metrics access, empty default value
--debug-pprof
: Enable pprof debugging, defaults to `false`
--debug-zpages
: Enable zpages debugging, defaults to `false`
--http-addr
: Address to bind http server, defaults to `0.0.0.0:9100`
--http-root
: Root path of http server, defaults to `/`
--asset-path
: Path to custom assets, empty default value
--web-config
: Path to graph-explorer config, empty default value
--web-config-server
: Server URL, defaults to `http://localhost:9135`
--web-config-theme
: Theme, defaults to `owncloud`
--web-config-version
: Version, defaults to `0.1.0`
--web-config-apps
: Use multiple times to provide multiple apps
--oidc-metadata-url
: OpenID Connect metadata URL, defaults to `http://localhost:9130/.well-known/openid-configuration`
--oidc-authority
: OpenID Connect authority, defaults to `http://localhost:9130`
--oidc-client-id
: OpenID Connect client ID, defaults to `graph-explorer`
--oidc-response-type
: OpenID Connect response type, defaults to `code`
--oidc-scope
: OpenID Connect scope, defaults to `openid profile email`
In case you want to render any additional properties in the config.json you can provide a custom config.json using eg. `ocis-graph-explorer server --web-config=/path/to/config.json`
##### Health
--debug-addr
: Address to debug endpoint, defaults to `0.0.0.0:9104`
#### Configuration file
So far we support the file formats `JSON` and `YAML`, if you want to get a full example configuration just take a look at [our repository](https://github.com/owncloud/ocis-graph-explorer/tree/master/config), there you can always see the latest configuration format. These example configurations include all available options and the default values. The configuration file will be automatically loaded if it's placed at `/etc/ocis/graph-explorer.yml`, `${HOME}/.ocis/graph-explorer.yml` or `$(pwd)/config/graph-explorer.yml`.
### Usage
The program provides a few sub-commands on execution. The available configuration methods have already been mentioned above. Generally you can always see a formated help output if you execute the binary via `ocis-graph-explorer --help`.
#### Server
The server command is used to start the http and debug server on two addresses within a single process. The http server is serving the general webservice while the debug server is used for health check, readiness check and to server the metrics mentioned below. For further help please execute:
{{< highlight txt >}}
ocis-graph-explorer server --help
{{< / highlight >}}
#### Health
The health command is used to execute a health check, if the exit code equals zero the service should be up and running, if the exist code is greater than zero the service is not in a healthy state. Generally this command is used within our Docker containers, it could also be used within Kubernetes.
{{< highlight txt >}}
ocis-graph-explorer health --help
{{< / highlight >}}
### Metrics
This service provides some [Prometheus](https://prometheus.io/) metrics through the debug endpoint, you can optionally secure the metrics endpoint by some random token, which got to be configured through one of the flag `--debug-token` or the environment variable `GRAPH_EXPLORER_DEBUG_TOKEN` mentioned above. By default the metrics endpoint is bound to `http://0.0.0.0:9104/metrics`.
go_gc_duration_seconds
: A summary of the GC invocation durations
go_gc_duration_seconds_sum
: A summary of the GC invocation durations
go_gc_duration_seconds_count
: A summary of the GC invocation durations
go_goroutines
: Number of goroutines that currently exist
go_info
: Information about the Go environment
go_memstats_alloc_bytes
: Number of bytes allocated and still in use
go_memstats_alloc_bytes_total
: Total number of bytes allocated, even if freed
go_memstats_buck_hash_sys_bytes
: Number of bytes used by the profiling bucket hash table
go_memstats_frees_total
: Total number of frees
go_memstats_gc_cpu_fraction
: The fraction of this program's available CPU time used by the GC since the program started
go_memstats_gc_sys_bytes
: Number of bytes used for garbage collection system metadata
go_memstats_heap_alloc_bytes
: Number of heap bytes allocated and still in use
go_memstats_heap_idle_bytes
: Number of heap bytes waiting to be used
go_memstats_heap_inuse_bytes
: Number of heap bytes that are in use
go_memstats_heap_objects
: Number of allocated objects
go_memstats_heap_released_bytes
: Number of heap bytes released to OS
go_memstats_heap_sys_bytes
: Number of heap bytes obtained from system
go_memstats_last_gc_time_seconds
: Number of seconds since 1970 of last garbage collection
go_memstats_lookups_total
: Total number of pointer lookups
go_memstats_mallocs_total
: Total number of mallocs
go_memstats_mcache_inuse_bytes
: Number of bytes in use by mcache structures
go_memstats_mcache_sys_bytes
: Number of bytes used for mcache structures obtained from system
go_memstats_mspan_inuse_bytes
: Number of bytes in use by mspan structures
go_memstats_mspan_sys_bytes
: Number of bytes used for mspan structures obtained from system
go_memstats_next_gc_bytes
: Number of heap bytes when next garbage collection will take place
go_memstats_other_sys_bytes
: Number of bytes used for other system allocations
go_memstats_stack_inuse_bytes
: Number of bytes in use by the stack allocator
go_memstats_stack_sys_bytes
: Number of bytes obtained from system for stack allocator
go_memstats_sys_bytes
: Number of bytes obtained from system
go_threads
: Number of OS threads created
promhttp_metric_handler_requests_in_flight
: Current number of scrapes being served
promhttp_metric_handler_requests_total
: Total number of scrapes by HTTP status code

View File

@@ -1,8 +0,0 @@
---
title: "License"
date: 2018-05-02T00:00:00+00:00
anchor: "license"
weight: 40
---
This project is licensed under the [Apache 2.0](https://github.com/owncloud/ocis-graph-explorer/blob/master/LICENSE) license. For the license of the used libraries you have to check the respective sources.

View File

@@ -1,57 +0,0 @@
<!DOCTYPE html>
<html id="html" lang="{{ with .Site.LanguageCode }}{{ . }}{{ else }}en-US{{ end }}">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width"/>
<title>
{{ .Site.Title }}
</title>
<meta name="description" content="{{ with .Site.Params.description }}{{ . }}{{ end }}">
<meta name="author" content="{{ with .Site.Params.author }}{{ . }}{{ end }}">
{{ partial "style.html" . }}
</head>
<body>
<section id="Menu">
<header>
<h1>
{{ .Site.Title }}
</h1>
<p>
{{ .Site.Params.description }}
</p>
</header>
<nav>
{{ range .Data.Pages.ByWeight }}
<a href="#{{ .Params.anchor }}">
{{ .Title }}
</a>
{{ end }}
</nav>
</section>
{{ range .Data.Pages.ByWeight }}
<section id="{{ .Params.anchor }}">
<h2>
<a href="#{{ .Params.anchor }}">
{{ .Title }}
</a>
<small>
<a href="#html">
Back to Top
</a>
</small>
</h2>
{{ .Content | markdownify }}
</section>
{{ end }}
</body>
</html>

View File

@@ -1,2 +0,0 @@
<link rel="stylesheet" href="syntax.css" />
<link rel="stylesheet" href="styles.css" />

View File

@@ -1,338 +0,0 @@
body,
html {
cursor: default;
}
body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
form,
fieldset,
input,
textarea,
p,
blockquote,
th,
td {
margin: 0;
padding: 0;
}
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
:before,
:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
img,
object,
embed {
max-width: 100%;
height: auto;
}
object,
embed {
height: 100%;
}
img {
margin: 1.25% 0;
-ms-interpolation-mode: bicubic;
}
html {
background-color: #F0F1F3;
padding: 2%;
}
body {
font-size: 16px;
line-height: 1.6;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
color: #242424;
max-width: 800px;
margin: 5% auto;
}
body::after {
clear: both;
content: "";
display: table;
}
header {
margin-bottom: 8%;
}
footer {
text-align: center;
}
h1,
h2,
h3,
h4,
h5,
h2 a {
color: #263A48;
font-weight: 500;
text-decoration: none;
}
h1,
h2 {
font-size: 36px;
padding-bottom: 0.3em;
margin-bottom: 0.4em;
border-bottom: 1px solid #eee
}
h2 {
font-size: 22px;
padding-bottom: 0.6em;
margin-bottom: 0.6em;
margin-top: 2.5em;
}
h3 {
font-size: 18px;
margin-bottom: 0.3em;
}
h2 small a {
color: #98999C;
font-size: 15px;
font-weight: normal;
float: right;
position: absolute;
top: 15px;
right: 20px;
}
section {
background: #fff;
margin-bottom: 1%;
position: relative;
padding: 6% 8%;
}
blockquote {
border-left: 3px solid #d54e21;
font-size: 16px;
padding: 0 0 0 20px;
color: #d54e21;
}
blockquote a {
color: #d54e21;
font-weight: 500;
}
blockquote code {
color: #d54e21;
}
.highlight pre {
padding: 10px;
}
.highlight {
margin-bottom: 4%;
}
a {
color: #1e8cbe;
text-decoration: underline;
}
a:hover {
color: #d54e21;
}
ul {
list-style: none;
}
ol {
list-style: number;
}
ol li {
color: #98999C;
margin-bottom: 5px;
}
ol li:last-child {
margin-bottom: 0;
}
p,
ul,
ol,
blockquote {
margin-bottom: 4%;
}
ul ul {
padding-top: 0;
margin-bottom: 0;
margin-left: 4%;
}
ul ul li:before {
content: '-';
display: inline-block;
padding-right: 2%;
}
ul.col-2 {
color: #98999C;
-webkit-column-count: 2;
-moz-column-count: 2;
column-count: 2;
-webkit-column-gap: 20px;
-moz-column-gap: 20px;
column-gap: 20px;
}
dl dt {
font-weight: bold;
}
dl dd {
padding-left: 10px;
}
@media screen and (min-width: 500px) {
ul.col-2 {
-webkit-column-count: 3;
-moz-column-count: 3;
column-count: 3;
-webkit-column-gap: 20px;
-moz-column-gap: 20px;
column-gap: 20px;
}
}
nav {
background: #F0F1F3;
min-width: 215px;
margin-bottom: 5px;
margin-top: 15px;
}
nav:first-of-type a {
color: #d54e21;
border-radius: 0;
}
nav:first-of-type a:hover {
color: #d54e21;
}
nav:first-of-type a:before {
background-color: #d54e21;
}
nav.affix {
position: fixed;
top: 20px;
}
nav.affix-bottom {
position: absolute;
}
nav a {
border-radius: 3px;
font-size: 15px;
display: block;
cursor: pointer;
font-weight: 500;
position: relative;
text-decoration: none;
padding: 10px 12px;
width: 100%;
padding-right: 3px;
border-bottom: 2px solid #fff;
}
nav a:before {
content: '';
width: 4px;
display: block;
left: 0;
position: absolute;
height: 100%;
display: none;
background: #1e8cbe;
top: 0;
}
nav a:hover {
background-color: #E6E8EA;
color: #1e8cbe;
text-decoration: underline;
}
nav a:hover:before {
display: block;
}
nav a:last-of-type {
border-bottom: none;
}
.gist {
margin-top: 5.1%;
margin-bottom: 5%;
}
@media screen and (max-width: 1050px) {
body {
margin: 0 auto;
}
}
@media screen and (max-width: 767px) {
header span {
display: none;
}
h1 {
font-size: 26px;
}
h2 {
font-size: 20px;
}
}
@media screen and (max-width: 514px) {
p,
ul,
ol,
blockquote {
margin-bottom: 8%;
}
}

View File

@@ -1,59 +0,0 @@
/* Background */ .chroma { color: #f8f8f2; background-color: #272822 }
/* Error */ .chroma .err { color: #960050; background-color: #1e0010 }
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc }
/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em; }
/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em; }
/* Keyword */ .chroma .k { color: #66d9ef }
/* KeywordConstant */ .chroma .kc { color: #66d9ef }
/* KeywordDeclaration */ .chroma .kd { color: #66d9ef }
/* KeywordNamespace */ .chroma .kn { color: #f92672 }
/* KeywordPseudo */ .chroma .kp { color: #66d9ef }
/* KeywordReserved */ .chroma .kr { color: #66d9ef }
/* KeywordType */ .chroma .kt { color: #66d9ef }
/* NameAttribute */ .chroma .na { color: #a6e22e }
/* NameClass */ .chroma .nc { color: #a6e22e }
/* NameConstant */ .chroma .no { color: #66d9ef }
/* NameDecorator */ .chroma .nd { color: #a6e22e }
/* NameException */ .chroma .ne { color: #a6e22e }
/* NameFunction */ .chroma .nf { color: #a6e22e }
/* NameOther */ .chroma .nx { color: #a6e22e }
/* NameTag */ .chroma .nt { color: #f92672 }
/* Literal */ .chroma .l { color: #ae81ff }
/* LiteralDate */ .chroma .ld { color: #e6db74 }
/* LiteralString */ .chroma .s { color: #e6db74 }
/* LiteralStringAffix */ .chroma .sa { color: #e6db74 }
/* LiteralStringBacktick */ .chroma .sb { color: #e6db74 }
/* LiteralStringChar */ .chroma .sc { color: #e6db74 }
/* LiteralStringDelimiter */ .chroma .dl { color: #e6db74 }
/* LiteralStringDoc */ .chroma .sd { color: #e6db74 }
/* LiteralStringDouble */ .chroma .s2 { color: #e6db74 }
/* LiteralStringEscape */ .chroma .se { color: #ae81ff }
/* LiteralStringHeredoc */ .chroma .sh { color: #e6db74 }
/* LiteralStringInterpol */ .chroma .si { color: #e6db74 }
/* LiteralStringOther */ .chroma .sx { color: #e6db74 }
/* LiteralStringRegex */ .chroma .sr { color: #e6db74 }
/* LiteralStringSingle */ .chroma .s1 { color: #e6db74 }
/* LiteralStringSymbol */ .chroma .ss { color: #e6db74 }
/* LiteralNumber */ .chroma .m { color: #ae81ff }
/* LiteralNumberBin */ .chroma .mb { color: #ae81ff }
/* LiteralNumberFloat */ .chroma .mf { color: #ae81ff }
/* LiteralNumberHex */ .chroma .mh { color: #ae81ff }
/* LiteralNumberInteger */ .chroma .mi { color: #ae81ff }
/* LiteralNumberIntegerLong */ .chroma .il { color: #ae81ff }
/* LiteralNumberOct */ .chroma .mo { color: #ae81ff }
/* Operator */ .chroma .o { color: #f92672 }
/* OperatorWord */ .chroma .ow { color: #f92672 }
/* Comment */ .chroma .c { color: #75715e }
/* CommentHashbang */ .chroma .ch { color: #75715e }
/* CommentMultiline */ .chroma .cm { color: #75715e }
/* CommentSingle */ .chroma .c1 { color: #75715e }
/* CommentSpecial */ .chroma .cs { color: #75715e }
/* CommentPreproc */ .chroma .cp { color: #75715e }
/* CommentPreprocFile */ .chroma .cpf { color: #75715e }
/* GenericDeleted */ .chroma .gd { color: #f92672 }
/* GenericEmph */ .chroma .ge { font-style: italic }
/* GenericInserted */ .chroma .gi { color: #a6e22e }
/* GenericStrong */ .chroma .gs { font-weight: bold }
/* GenericSubheading */ .chroma .gu { color: #75715e }

View File

@@ -0,0 +1,76 @@
---
title: "Configuration"
date: "{{ date "2006-01-02T15:04:05-0700" now }}"
weight: 20
geekdocRepo: https://github.com/owncloud/ocis
geekdocEditPath: edit/master/graph-explorer/templates
geekdocFilePath: CONFIGURATION.tmpl
---
{{- define "options"}}
{{ $fnName := (last . ).Flags -}}
{{ range $opt := first . }}{{ with list $fnName $opt -}}
{{ $o := last . -}}
{{ if eq $o.FnName $fnName -}}
-{{ $o.Name }} | {{ range $i, $e := $o.Env }} {{ if $i }}, {{ end }}${{ $e }}{{ end }}
: {{ $o.Usage }}. {{- if $o.Default }} Default: `{{ $o.Default }}`.{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end }}
{{`{{< toc >}}`}}
## Configuration
### Configuration using config files
Out of the box extensions will attempt to read configuration details from:
```console
/etc/ocis
$HOME/.ocis
./config
```
For this configuration to be picked up, have a look at your extension `root` command and look for which default config name it has assigned. *i.e: ocis-proxy reads `proxy.json | yaml | toml ...`*.
So far we support the file formats `JSON` and `YAML`, if you want to get a full example configuration just take a look at [our repository](https://github.com/owncloud/ocis/tree/master/graph-explorer/config), there you can always see the latest configuration format. These example configurations include all available options and the default values. The configuration file will be automatically loaded if it's placed at `/etc/ocis/ocis.yml`, `${HOME}/.ocis/ocis.yml` or `$(pwd)/config/ocis.yml`.
### Environment variables
If you prefer to configure the service with environment variables you can see the available variables below.
If multiple variables are listed for one option, they are in order of precedence. This means the leftmost variable will always win if given.
### Commandline flags
If you prefer to configure the service with commandline flags you can see the available variables below. Command line flags are only working when calling the subcommand directly.
{{ $options := .Options -}}
{{ range $com := .Commands }}{{ with (list $options $com) -}}
{{ $c := last . -}}
{{ if eq $c.Name "graph-explorer" -}}
## Root Command
{{ $c.Usage }}
Usage: `graph-explorer [global options] command [command options] [arguments...]`
{{ template "options" . -}}
## Sub Commands
{{ end -}}
{{ end -}}
{{ end -}}
{{- range $com := .Commands }}{{ with (list $options $com) -}}
{{- $c := last . }}
{{- if ne $c.Name "graph-explorer" -}}
### graph-explorer {{ $c.Name }}
{{ $c.Usage }}
Usage: `graph-explorer {{ $c.Name }} [command options] [arguments...]`
{{ template "options" . }}
{{- end -}}
{{- end -}}
{{- end -}}

2
graph/.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
*
!bin/

202
graph/LICENSE Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

188
graph/Makefile Normal file
View File

@@ -0,0 +1,188 @@
SHELL := bash
NAME := graph
IMPORT := github.com/owncloud/ocis/$(NAME)
BIN := bin
DIST := dist
ifeq ($(OS), Windows_NT)
EXECUTABLE := $(NAME).exe
UNAME := Windows
else
EXECUTABLE := $(NAME)
UNAME := $(shell uname -s)
endif
ifeq ($(UNAME), Darwin)
GOBUILD ?= go build -i
else
GOBUILD ?= go build
endif
PACKAGES ?= $(shell go list ./...)
SOURCES ?= $(shell find . -name "*.go" -type f -not -path "./node_modules/*")
GENERATE ?= $(PACKAGES)
TAGS ?=
ifndef OUTPUT
ifneq ($(DRONE_TAG),)
OUTPUT ?= $(subst v,,$(DRONE_TAG))
else
OUTPUT ?= testing
endif
endif
ifndef VERSION
ifneq ($(DRONE_TAG),)
VERSION ?= $(subst v,,$(DRONE_TAG))
else
VERSION ?= $(shell git rev-parse --short HEAD)
endif
endif
ifndef DATE
DATE := $(shell date -u '+%Y%m%d')
endif
LDFLAGS += -s -w -X "$(IMPORT)/pkg/version.String=$(VERSION)" -X "$(IMPORT)/pkg/version.Date=$(DATE)"
DEBUG_LDFLAGS += -X "$(IMPORT)/pkg/version.String=$(VERSION)" -X "$(IMPORT)/pkg/version.Date=$(DATE)"
GCFLAGS += all=-N -l
.PHONY: all
all: build
.PHONY: sync
sync:
go mod download
.PHONY: clean
clean:
go clean -i ./...
rm -rf $(BIN) $(DIST)
.PHONY: go-mod-tidy
go-mod-tidy:
@go mod tidy
.PHONY: fmt
fmt:
gofmt -s -w $(SOURCES)
.PHONY: vet
vet:
go vet $(PACKAGES)
.PHONY: staticcheck
staticcheck:
go run honnef.co/go/tools/cmd/staticcheck -tags '$(TAGS)' $(PACKAGES)
.PHONY: lint
lint:
for PKG in $(PACKAGES); do go run golang.org/x/lint/golint -set_exit_status $$PKG || exit 1; done;
.PHONY: generate
generate:
go generate $(GENERATE)
.PHONY: changelog
changelog:
go run github.com/restic/calens >| CHANGELOG.md
.PHONY: test
test:
go run github.com/haya14busa/goverage -v -coverprofile coverage.out $(PACKAGES)
.PHONY: go-coverage
go-coverage:
@if [ ! -f coverage.out ]; then $(MAKE) test &>/dev/null; fi;
@go tool cover -func coverage.out | tail -1 | grep -Eo "[0-9]+\.[0-9]+"
.PHONY: install
install: $(SOURCES)
go install -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/$(NAME)
.PHONY: build
build: $(BIN)/$(EXECUTABLE) $(BIN)/$(EXECUTABLE)-debug
$(BIN)/$(EXECUTABLE): $(SOURCES)
$(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME)
$(BIN)/$(EXECUTABLE)-debug: $(SOURCES)
$(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(DEBUG_LDFLAGS)' -gcflags '$(GCFLAGS)' -o $@ ./cmd/$(NAME)
.PHONY: release
release: release-dirs release-linux release-windows release-darwin release-copy release-check
.PHONY: release-dirs
release-dirs:
mkdir -p $(DIST)/binaries $(DIST)/release
.PHONY: release-linux
release-linux: release-dirs
go run github.com/mitchellh/gox -tags 'netgo $(TAGS)' -ldflags '-extldflags "-static" $(LDFLAGS)' -os 'linux' -arch 'amd64 386 arm64 arm' -output '$(DIST)/binaries/$(EXECUTABLE)-$(OUTPUT)-{{.OS}}-{{.Arch}}' ./cmd/$(NAME)
.PHONY: release-windows
release-windows: release-dirs
go run github.com/mitchellh/gox -tags 'netgo $(TAGS)' -ldflags '-extldflags "-static" $(LDFLAGS)' -os 'windows' -arch 'amd64' -output '$(DIST)/binaries/$(EXECUTABLE)-$(OUTPUT)-{{.OS}}-{{.Arch}}' ./cmd/$(NAME)
.PHONY: release-darwin
release-darwin: release-dirs
go run github.com/mitchellh/gox -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -os 'darwin' -arch 'amd64' -output '$(DIST)/binaries/$(EXECUTABLE)-$(OUTPUT)-{{.OS}}-{{.Arch}}' ./cmd/$(NAME)
.PHONY: release-copy
release-copy:
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
.PHONY: release-check
release-check:
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
.PHONY: release-finish
release-finish: release-copy release-check
.PHONY: config-docs-generate
config-docs-generate:
go run github.com/owncloud/flaex >| ../docs/extensions/$(NAME)/configuration.md
.PHONY: watch
watch:
go run github.com/cespare/reflex -c reflex.conf
# $(GOPATH)/bin/protoc-gen-go:
# GO111MODULE=off go get -v github.com/golang/protobuf/protoc-gen-go
# $(GOPATH)/bin/protoc-gen-micro:
# GO111MODULE=off go get -v github.com/micro/protoc-gen-micro
# $(GOPATH)/bin/protoc-gen-microweb:
# GO111MODULE=off go get -v github.com/webhippie/protoc-gen-microweb
# $(GOPATH)/bin/protoc-gen-swagger:
# GO111MODULE=off go get -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
# pkg/proto/v0/example.pb.go: pkg/proto/v0/example.proto
# protoc \
# -I=third_party/ \
# -I=pkg/proto/v0/ \
# --go_out=pkg/proto/v0 example.proto
# pkg/proto/v0/example.pb.micro.go: pkg/proto/v0/example.proto
# protoc \
# -I=third_party/ \
# -I=pkg/proto/v0/ \
# --micro_out=pkg/proto/v0 example.proto
# pkg/proto/v0/example.pb.web.go: pkg/proto/v0/example.proto
# protoc \
# -I=third_party/ \
# -I=pkg/proto/v0/ \
# --microweb_out=pkg/proto/v0 example.proto
# pkg/proto/v0/example.swagger.json: pkg/proto/v0/example.proto
# protoc \
# -I=third_party/ \
# -I=pkg/proto/v0/ \
# --swagger_out=pkg/proto/v0 example.proto
# .PHONY: protobuf
# protobuf: $(GOPATH)/bin/protoc-gen-go $(GOPATH)/bin/protoc-gen-micro $(GOPATH)/bin/protoc-gen-microweb $(GOPATH)/bin/protoc-gen-swagger pkg/proto/v0/example.pb.go pkg/proto/v0/example.pb.micro.go pkg/proto/v0/example.pb.web.go pkg/proto/v0/example.swagger.json

13
graph/cmd/graph/main.go Normal file
View File

@@ -0,0 +1,13 @@
package main
import (
"os"
"github.com/owncloud/ocis/graph/pkg/command"
)
func main() {
if err := command.Execute(); err != nil {
os.Exit(1)
}
}

18
graph/config/example.json Normal file
View File

@@ -0,0 +1,18 @@
{
"debug": {
"addr": "0.0.0.0:9124",
"token": "",
"pprof": false,
"zpages": false
},
"http": {
"addr": "0.0.0.0:9120"
},
"tracing": {
"enabled": false,
"type": "jaeger",
"endpoint": "localhost:6831",
"collector": "http://localhost:14268/api/traces",
"service": "graph"
}
}

18
graph/config/example.yml Normal file
View File

@@ -0,0 +1,18 @@
---
debug:
addr: 0.0.0.0:9124
token:
pprof: false
zpages: false
http:
addr: 0.0.0.0:9120
tracing:
enabled: false
type: jaeger
endpoint: localhost:6831
collector: http://localhost:14268/api/traces
service: graph
...

View File

@@ -0,0 +1,19 @@
FROM amd64/alpine:latest
RUN apk update && \
apk upgrade && \
apk add ca-certificates mailcap && \
rm -rf /var/cache/apk/* && \
echo 'hosts: files dns' >| /etc/nsswitch.conf
LABEL maintainer="ownCloud GmbH <devops@owncloud.com>" \
org.label-schema.name="oCIS Graph" \
org.label-schema.vendor="ownCloud GmbH" \
org.label-schema.schema-version="1.0"
EXPOSE 9120 9124
ENTRYPOINT ["/usr/bin/ocis-graph"]
CMD ["server"]
COPY bin/ocis-graph /usr/bin/ocis-graph

View File

@@ -0,0 +1,19 @@
FROM arm32v6/alpine:latest
RUN apk update && \
apk upgrade && \
apk add ca-certificates mailcap && \
rm -rf /var/cache/apk/* && \
echo 'hosts: files dns' >| /etc/nsswitch.conf
LABEL maintainer="ownCloud GmbH <devops@owncloud.com>" \
org.label-schema.name="oCIS Graph" \
org.label-schema.vendor="ownCloud GmbH" \
org.label-schema.schema-version="1.0"
EXPOSE 9120 9124
ENTRYPOINT ["/usr/bin/ocis-graph"]
CMD ["server"]
COPY bin/ocis-graph /usr/bin/ocis-graph

View File

@@ -0,0 +1,19 @@
FROM arm64v8/alpine:latest
RUN apk update && \
apk upgrade && \
apk add ca-certificates mailcap && \
rm -rf /var/cache/apk/* && \
echo 'hosts: files dns' >| /etc/nsswitch.conf
LABEL maintainer="ownCloud GmbH <devops@owncloud.com>" \
org.label-schema.name="oCIS Graph" \
org.label-schema.vendor="ownCloud GmbH" \
org.label-schema.schema-version="1.0"
EXPOSE 9120 9124
ENTRYPOINT ["/usr/bin/ocis-graph"]
CMD ["server"]
COPY bin/ocis-graph /usr/bin/ocis-graph

View File

@@ -0,0 +1,22 @@
image: owncloud/ocis-graph:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
- image: owncloud/ocis-graph:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
- image: owncloud/ocis-graph:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
platform:
architecture: arm64
variant: v8
os: linux
- image: owncloud/ocis-graph:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
platform:
architecture: arm
variant: v6
os: linux

26
graph/go.mod Normal file
View File

@@ -0,0 +1,26 @@
module github.com/owncloud/ocis/graph
go 1.13
require (
contrib.go.opencensus.io/exporter/jaeger v0.2.1
contrib.go.opencensus.io/exporter/ocagent v0.7.0
contrib.go.opencensus.io/exporter/zipkin v0.1.2
github.com/cs3org/go-cs3apis v0.0.0-20200810113633-b00aca449666
github.com/cs3org/reva v1.1.0
github.com/go-chi/chi v4.1.2+incompatible
github.com/go-chi/render v1.0.1
github.com/go-ldap/ldap/v3 v3.2.3
github.com/gogo/protobuf v1.3.1 // indirect
github.com/micro/cli/v2 v2.1.2
github.com/oklog/run v1.1.0
github.com/openzipkin/zipkin-go v0.2.2
github.com/owncloud/ocis-pkg/v2 v2.2.1
github.com/spf13/afero v1.3.4 // indirect
github.com/spf13/viper v1.7.1
github.com/yaegashi/msgraph.go v0.1.4
go.opencensus.io v0.22.4
google.golang.org/grpc v1.31.0
)
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0

1475
graph/go.sum Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
package command
import (
"fmt"
"net/http"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis/graph/pkg/config"
"github.com/owncloud/ocis/graph/pkg/flagset"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "health",
Usage: "Check health status",
Flags: flagset.HealthWithConfig(cfg),
Action: func(c *cli.Context) error {
logger := NewLogger(cfg)
resp, err := http.Get(
fmt.Sprintf(
"http://%s/healthz",
cfg.Debug.Addr,
),
)
if err != nil {
logger.Fatal().
Err(err).
Msg("Failed to request health check")
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
logger.Fatal().
Int("code", resp.StatusCode).
Msg("Health seems to be in bad state")
}
logger.Debug().
Int("code", resp.StatusCode).
Msg("Health got a good state")
return nil
},
}
}

108
graph/pkg/command/root.go Normal file
View File

@@ -0,0 +1,108 @@
package command
import (
"os"
"strings"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis/graph/pkg/config"
"github.com/owncloud/ocis/graph/pkg/flagset"
"github.com/owncloud/ocis/graph/pkg/version"
"github.com/spf13/viper"
)
// Execute is the entry point for the ocis-graph command.
func Execute() error {
cfg := config.New()
app := &cli.App{
Name: "graph",
Version: version.String,
Usage: "Serve Graph API for oCIS",
Compiled: version.Compiled(),
Authors: []*cli.Author{
{
Name: "ownCloud GmbH",
Email: "support@owncloud.com",
},
},
Flags: flagset.RootWithConfig(cfg),
Before: func(c *cli.Context) error {
return ParseConfig(c, cfg)
},
Commands: []*cli.Command{
Server(cfg),
Health(cfg),
},
}
cli.HelpFlag = &cli.BoolFlag{
Name: "help,h",
Usage: "Show the help",
}
cli.VersionFlag = &cli.BoolFlag{
Name: "version,v",
Usage: "Print the version",
}
return app.Run(os.Args)
}
// NewLogger initializes a service-specific logger instance.
func NewLogger(cfg *config.Config) log.Logger {
return log.NewLogger(
log.Name("graph"),
log.Level(cfg.Log.Level),
log.Pretty(cfg.Log.Pretty),
log.Color(cfg.Log.Color),
)
}
// ParseConfig reads graph configuration from fs.
func ParseConfig(c *cli.Context, cfg *config.Config) error {
logger := NewLogger(cfg)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetEnvPrefix("GRAPH")
viper.AutomaticEnv()
if c.IsSet("config-file") {
viper.SetConfigFile(c.String("config-file"))
} else {
viper.SetConfigName("graph")
viper.AddConfigPath("/etc/ocis")
viper.AddConfigPath("$HOME/.ocis")
viper.AddConfigPath("./config")
}
if err := viper.ReadInConfig(); err != nil {
switch err.(type) {
case viper.ConfigFileNotFoundError:
logger.Info().
Msg("Continue without config")
case viper.UnsupportedConfigError:
logger.Fatal().
Err(err).
Msg("Unsupported config type")
default:
logger.Fatal().
Err(err).
Msg("Failed to read config")
}
}
if err := viper.Unmarshal(&cfg); err != nil {
logger.Fatal().
Err(err).
Msg("Failed to parse config")
}
return nil
}

219
graph/pkg/command/server.go Normal file
View File

@@ -0,0 +1,219 @@
package command
import (
"context"
"os"
"os/signal"
"strings"
"time"
"contrib.go.opencensus.io/exporter/jaeger"
"contrib.go.opencensus.io/exporter/ocagent"
"contrib.go.opencensus.io/exporter/zipkin"
"github.com/micro/cli/v2"
"github.com/oklog/run"
openzipkin "github.com/openzipkin/zipkin-go"
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
"github.com/owncloud/ocis/graph/pkg/config"
"github.com/owncloud/ocis/graph/pkg/flagset"
"github.com/owncloud/ocis/graph/pkg/metrics"
"github.com/owncloud/ocis/graph/pkg/server/debug"
"github.com/owncloud/ocis/graph/pkg/server/http"
"go.opencensus.io/stats/view"
"go.opencensus.io/trace"
)
// Server is the entrypoint for the server command.
func Server(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "server",
Usage: "Start integrated server",
Flags: flagset.ServerWithConfig(cfg),
Before: func(c *cli.Context) error {
if cfg.HTTP.Root != "/" {
cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/")
}
return ParseConfig(c, cfg)
},
Action: func(c *cli.Context) error {
logger := NewLogger(cfg)
if cfg.Tracing.Enabled {
switch t := cfg.Tracing.Type; t {
case "agent":
exporter, err := ocagent.NewExporter(
ocagent.WithReconnectionPeriod(5*time.Second),
ocagent.WithAddress(cfg.Tracing.Endpoint),
ocagent.WithServiceName(cfg.Tracing.Service),
)
if err != nil {
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create agent tracing")
return err
}
trace.RegisterExporter(exporter)
view.RegisterExporter(exporter)
case "jaeger":
exporter, err := jaeger.NewExporter(
jaeger.Options{
AgentEndpoint: cfg.Tracing.Endpoint,
CollectorEndpoint: cfg.Tracing.Collector,
ServiceName: cfg.Tracing.Service,
},
)
if err != nil {
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create jaeger tracing")
return err
}
trace.RegisterExporter(exporter)
case "zipkin":
endpoint, err := openzipkin.NewEndpoint(
cfg.Tracing.Service,
cfg.Tracing.Endpoint,
)
if err != nil {
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create zipkin tracing")
return err
}
exporter := zipkin.NewExporter(
zipkinhttp.NewReporter(
cfg.Tracing.Collector,
),
endpoint,
)
trace.RegisterExporter(exporter)
default:
logger.Warn().
Str("type", t).
Msg("Unknown tracing backend")
}
trace.ApplyConfig(
trace.Config{
DefaultSampler: trace.AlwaysSample(),
},
)
} else {
logger.Debug().
Msg("Tracing is not enabled")
}
var (
gr = run.Group{}
ctx, cancel = context.WithCancel(context.Background())
metrics = metrics.New()
)
defer cancel()
{
server, err := http.Server(
http.Logger(logger),
http.Context(ctx),
http.Config(cfg),
http.Metrics(metrics),
http.Flags(flagset.RootWithConfig(cfg)),
http.Flags(flagset.ServerWithConfig(cfg)),
)
if err != nil {
logger.Info().
Err(err).
Str("transport", "http").
Msg("Failed to initialize server")
return err
}
gr.Add(func() error {
return server.Run()
}, func(_ error) {
logger.Info().
Str("transport", "http").
Msg("Shutting down server")
cancel()
})
}
{
server, err := debug.Server(
debug.Logger(logger),
debug.Context(ctx),
debug.Config(cfg),
)
if err != nil {
logger.Info().
Err(err).
Str("transport", "debug").
Msg("Failed to initialize server")
return err
}
gr.Add(func() error {
return server.ListenAndServe()
}, func(_ error) {
ctx, timeout := context.WithTimeout(ctx, 5*time.Second)
defer timeout()
defer cancel()
if err := server.Shutdown(ctx); err != nil {
logger.Info().
Err(err).
Str("transport", "debug").
Msg("Failed to shutdown server")
} else {
logger.Info().
Str("transport", "debug").
Msg("Shutting down server")
}
})
}
{
stop := make(chan os.Signal, 1)
gr.Add(func() error {
signal.Notify(stop, os.Interrupt)
<-stop
return nil
}, func(err error) {
close(stop)
cancel()
})
}
return gr.Run()
},
}
}

View File

@@ -0,0 +1,72 @@
package config
// Log defines the available logging configuration.
type Log struct {
Level string
Pretty bool
Color bool
}
// Debug defines the available debug configuration.
type Debug struct {
Addr string
Token string
Pprof bool
Zpages bool
}
// HTTP defines the available http configuration.
type HTTP struct {
Addr string
Namespace string
Root string
}
// Tracing defines the available tracing configuration.
type Tracing struct {
Enabled bool
Type string
Endpoint string
Collector string
Service string
}
// Ldap defined the available LDAP configuration.
type Ldap struct {
Network string
Address string
UserName string
Password string
BaseDNUsers string
BaseDNGroups string
}
// OpenIDConnect defined the available OpenID Connect configuration.
type OpenIDConnect struct {
Endpoint string
Realm string
SigningAlgs []string
Insecure bool
}
// Reva defines all available REVA configuration.
type Reva struct {
Address string
}
// Config combines all available configuration parts.
type Config struct {
File string
Log Log
Debug Debug
HTTP HTTP
Tracing Tracing
Ldap Ldap
OpenIDConnect OpenIDConnect
Reva Reva
}
// New initializes a new configuration with or without defaults.
func New() *Config {
return &Config{}
}

26
graph/pkg/cs3/client.go Normal file
View File

@@ -0,0 +1,26 @@
package cs3
import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"google.golang.org/grpc"
)
func newConn(endpoint string) (*grpc.ClientConn, error) {
conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
if err != nil {
return nil, err
}
return conn, nil
}
// GetGatewayServiceClient returns a new cs3 gateway client
func GetGatewayServiceClient(endpoint string) (gateway.GatewayAPIClient, error) {
conn, err := newConn(endpoint)
if err != nil {
return nil, err
}
return gateway.NewGatewayAPIClient(conn), nil
}

View File

@@ -0,0 +1,209 @@
package flagset
import (
"github.com/micro/cli/v2"
"github.com/owncloud/ocis/graph/pkg/config"
)
// RootWithConfig applies cfg to the root flagset
func RootWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "config-file",
Value: "",
Usage: "Path to config file",
EnvVars: []string{"GRAPH_CONFIG_FILE"},
Destination: &cfg.File,
},
&cli.StringFlag{
Name: "log-level",
Value: "info",
Usage: "Set logging level",
EnvVars: []string{"GRAPH_LOG_LEVEL"},
Destination: &cfg.Log.Level,
},
&cli.BoolFlag{
Value: true,
Name: "log-pretty",
Usage: "Enable pretty logging",
EnvVars: []string{"GRAPH_LOG_PRETTY"},
Destination: &cfg.Log.Pretty,
},
&cli.BoolFlag{
Value: true,
Name: "log-color",
Usage: "Enable colored logging",
EnvVars: []string{"GRAPH_LOG_COLOR"},
Destination: &cfg.Log.Color,
},
}
}
// HealthWithConfig applies cfg to the root flagset
func HealthWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "debug-addr",
Value: "0.0.0.0:9124",
Usage: "Address to debug endpoint",
EnvVars: []string{"GRAPH_DEBUG_ADDR"},
Destination: &cfg.Debug.Addr,
},
}
}
// ServerWithConfig applies cfg to the root flagset
func ServerWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: "tracing-enabled",
Usage: "Enable sending traces",
EnvVars: []string{"GRAPH_TRACING_ENABLED"},
Destination: &cfg.Tracing.Enabled,
},
&cli.StringFlag{
Name: "tracing-type",
Value: "jaeger",
Usage: "Tracing backend type",
EnvVars: []string{"GRAPH_TRACING_TYPE"},
Destination: &cfg.Tracing.Type,
},
&cli.StringFlag{
Name: "tracing-endpoint",
Value: "",
Usage: "Endpoint for the agent",
EnvVars: []string{"GRAPH_TRACING_ENDPOINT"},
Destination: &cfg.Tracing.Endpoint,
},
&cli.StringFlag{
Name: "tracing-collector",
Value: "",
Usage: "Endpoint for the collector",
EnvVars: []string{"GRAPH_TRACING_COLLECTOR"},
Destination: &cfg.Tracing.Collector,
},
&cli.StringFlag{
Name: "tracing-service",
Value: "graph",
Usage: "Service name for tracing",
EnvVars: []string{"GRAPH_TRACING_SERVICE"},
Destination: &cfg.Tracing.Service,
},
&cli.StringFlag{
Name: "debug-addr",
Value: "0.0.0.0:9124",
Usage: "Address to bind debug server",
EnvVars: []string{"GRAPH_DEBUG_ADDR"},
Destination: &cfg.Debug.Addr,
},
&cli.StringFlag{
Name: "debug-token",
Value: "",
Usage: "Token to grant metrics access",
EnvVars: []string{"GRAPH_DEBUG_TOKEN"},
Destination: &cfg.Debug.Token,
},
&cli.BoolFlag{
Name: "debug-pprof",
Usage: "Enable pprof debugging",
EnvVars: []string{"GRAPH_DEBUG_PPROF"},
Destination: &cfg.Debug.Pprof,
},
&cli.BoolFlag{
Name: "debug-zpages",
Usage: "Enable zpages debugging",
EnvVars: []string{"GRAPH_DEBUG_ZPAGES"},
Destination: &cfg.Debug.Zpages,
},
&cli.StringFlag{
Name: "http-addr",
Value: "0.0.0.0:9120",
Usage: "Address to bind http server",
EnvVars: []string{"GRAPH_HTTP_ADDR"},
Destination: &cfg.HTTP.Addr,
},
&cli.StringFlag{
Name: "http-root",
Value: "/",
Usage: "Root path of http server",
EnvVars: []string{"GRAPH_HTTP_ROOT"},
Destination: &cfg.HTTP.Root,
},
&cli.StringFlag{
Name: "http-namespace",
Value: "com.owncloud.web",
Usage: "Set the base namespace for the http service for service discovery",
EnvVars: []string{"GRAPH_HTTP_NAMESPACE"},
Destination: &cfg.HTTP.Namespace,
},
&cli.StringFlag{
Name: "ldap-network",
Value: "tcp",
Usage: "Network protocol to use to connect to the Ldap server",
EnvVars: []string{"GRAPH_LDAP_NETWORK"},
Destination: &cfg.Ldap.Network,
},
&cli.StringFlag{
Name: "ldap-address",
Value: "localhost:9125",
Usage: "Address to connect to the Ldap server",
EnvVars: []string{"GRAPH_LDAP_ADDRESS"},
Destination: &cfg.Ldap.Address,
},
&cli.StringFlag{
Name: "ldap-username",
Value: "cn=admin,dc=example,dc=org",
Usage: "User to bind to the Ldap server",
EnvVars: []string{"GRAPH_LDAP_USERNAME"},
Destination: &cfg.Ldap.UserName,
},
&cli.StringFlag{
Name: "ldap-password",
Value: "admin",
Usage: "Password to bind to the Ldap server",
EnvVars: []string{"GRAPH_LDAP_PASSWORD"},
Destination: &cfg.Ldap.Password,
},
&cli.StringFlag{
Name: "ldap-basedn-users",
Value: "ou=users,dc=example,dc=org",
Usage: "BaseDN to look for users",
EnvVars: []string{"GRAPH_LDAP_BASEDN_USERS"},
Destination: &cfg.Ldap.BaseDNUsers,
},
&cli.StringFlag{
Name: "ldap-basedn-groups",
Value: "ou=groups,dc=example,dc=org",
Usage: "BaseDN to look for users",
EnvVars: []string{"GRAPH_LDAP_BASEDN_GROUPS"},
Destination: &cfg.Ldap.BaseDNGroups,
},
&cli.StringFlag{
Name: "oidc-endpoint",
Value: "https://localhost:9130",
Usage: "OpenIDConnect endpoint",
EnvVars: []string{"GRAPH_OIDC_ENDPOINT"},
Destination: &cfg.OpenIDConnect.Endpoint,
},
&cli.BoolFlag{
Name: "oidc-insecure",
Usage: "OpenIDConnect endpoint",
EnvVars: []string{"GRAPH_OIDC_INSECURE"},
Destination: &cfg.OpenIDConnect.Insecure,
},
&cli.StringFlag{
Name: "oidc-realm",
Value: "",
Usage: "OpenIDConnect realm",
EnvVars: []string{"GRAPH_OIDC_REALM"},
Destination: &cfg.OpenIDConnect.Realm,
},
&cli.StringFlag{
Name: "reva-gateway-addr",
Value: "127.0.0.1:9142",
Usage: "REVA Gateway Endpoint",
EnvVars: []string{"REVA_GATEWAY_ADDR"},
Destination: &cfg.Reva.Address,
},
}
}

View File

@@ -0,0 +1,32 @@
package metrics
var (
// Namespace defines the namespace for the defines metrics.
Namespace = "ocis"
// Subsystem defines the subsystem for the defines metrics.
Subsystem = "graph"
)
// Metrics defines the available metrics of this service.
type Metrics struct {
// Counter *prometheus.CounterVec
}
// New initializes the available metrics.
func New() *Metrics {
m := &Metrics{
// Counter: prometheus.NewCounterVec(prometheus.CounterOpts{
// Namespace: Namespace,
// Subsystem: Subsystem,
// Name: "greet_total",
// Help: "How many greeting requests processed",
// }, []string{}),
}
// prometheus.Register(
// m.Counter,
// )
return m
}

View File

@@ -0,0 +1,50 @@
package debug
import (
"context"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis/graph/pkg/config"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}

View File

@@ -0,0 +1,51 @@
package debug
import (
"io"
"net/http"
"github.com/owncloud/ocis-pkg/v2/service/debug"
"github.com/owncloud/ocis/graph/pkg/config"
"github.com/owncloud/ocis/graph/pkg/version"
)
// Server initializes the debug service and server.
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
return debug.NewService(
debug.Logger(options.Logger),
debug.Name("graph"),
debug.Version(version.String),
debug.Address(options.Config.Debug.Addr),
debug.Token(options.Config.Debug.Token),
debug.Pprof(options.Config.Debug.Pprof),
debug.Zpages(options.Config.Debug.Zpages),
debug.Health(health(options.Config)),
debug.Ready(ready(options.Config)),
), nil
}
// health implements the health check.
func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO(tboerger): check if services are up and running
io.WriteString(w, http.StatusText(http.StatusOK))
}
}
// ready implements the ready check.
func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO(tboerger): check if services are up and running
io.WriteString(w, http.StatusText(http.StatusOK))
}
}

View File

@@ -0,0 +1,76 @@
package http
import (
"context"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis/graph/pkg/config"
"github.com/owncloud/ocis/graph/pkg/metrics"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
Metrics *metrics.Metrics
Flags []cli.Flag
Namespace string
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Metrics provides a function to set the metrics option.
func Metrics(val *metrics.Metrics) Option {
return func(o *Options) {
o.Metrics = val
}
}
// Flags provides a function to set the flags option.
func Flags(val []cli.Flag) Option {
return func(o *Options) {
o.Flags = append(o.Flags, val...)
}
}
// Namespace provides a function to set the Namespace option.
func Namespace(val string) Option {
return func(o *Options) {
o.Namespace = val
}
}

View File

@@ -0,0 +1,63 @@
package http
import (
"github.com/owncloud/ocis-pkg/v2/middleware"
"github.com/owncloud/ocis-pkg/v2/oidc"
"github.com/owncloud/ocis-pkg/v2/service/http"
svc "github.com/owncloud/ocis/graph/pkg/service/v0"
"github.com/owncloud/ocis/graph/pkg/version"
)
// Server initializes the http service and server.
func Server(opts ...Option) (http.Service, error) {
options := newOptions(opts...)
service := http.NewService(
http.Logger(options.Logger),
http.Namespace(options.Config.HTTP.Namespace),
http.Name("graph"),
http.Version(version.String),
http.Address(options.Config.HTTP.Addr),
http.Context(options.Context),
http.Flags(options.Flags...),
)
handle := svc.NewService(
svc.Logger(options.Logger),
svc.Config(options.Config),
svc.Middleware(
middleware.RealIP,
middleware.RequestID,
middleware.Cache,
middleware.Cors,
middleware.Secure,
middleware.Version(
"graph",
version.String,
),
middleware.Logger(
options.Logger,
),
middleware.OpenIDConnect(
oidc.Endpoint(options.Config.OpenIDConnect.Endpoint),
oidc.Realm(options.Config.OpenIDConnect.Realm),
oidc.Insecure(options.Config.OpenIDConnect.Insecure),
oidc.Logger(options.Logger),
),
),
)
{
handle = svc.NewInstrument(handle, options.Metrics)
handle = svc.NewLogging(handle, options.Logger)
handle = svc.NewTracing(handle)
}
service.Handle(
"/",
handle,
)
service.Init()
return service, nil
}

View File

@@ -0,0 +1,140 @@
package svc
import (
"github.com/go-chi/render"
"google.golang.org/grpc/metadata"
"net/http"
"strings"
"time"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/token"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
)
func getToken(r *http.Request) string {
// 1. check Authorization header
hdr := r.Header.Get("Authorization")
t := strings.TrimPrefix(hdr, "Bearer ")
if t != "" {
return t
}
// TODO 2. check form encoded body parameter for POST requests, see https://tools.ietf.org/html/rfc6750#section-2.2
// 3. check uri query parameter, see https://tools.ietf.org/html/rfc6750#section-2.3
tokens, ok := r.URL.Query()["access_token"]
if !ok || len(tokens[0]) < 1 {
return ""
}
return tokens[0]
}
// GetRootDriveChildren implements the Service interface.
func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
g.logger.Info().Msgf("Calling GetRootDriveChildren")
accessToken := getToken(r)
if accessToken == "" {
g.logger.Error().Msg("no access token provided in request")
w.WriteHeader(http.StatusForbidden)
return
}
ctx := r.Context()
fn := "/home"
client, err := g.GetClient()
if err != nil {
g.logger.Err(err).Msg("error getting grpc client")
w.WriteHeader(http.StatusInternalServerError)
return
}
// get reva token
authReq := &gateway.AuthenticateRequest{
Type: "bearer",
ClientSecret: accessToken,
}
authRes, _ := client.Authenticate(ctx, authReq);
ctx = token.ContextSetToken(ctx, authRes.Token)
ctx = metadata.AppendToOutgoingContext(ctx, "x-access-token", authRes.Token)
g.logger.Info().Msgf("provides access token %v", ctx)
ref := &storageprovider.Reference{
Spec: &storageprovider.Reference_Path{Path: fn},
}
req := &storageprovider.ListContainerRequest{
Ref: ref,
}
res, err := client.ListContainer(ctx, req)
if err != nil {
g.logger.Error().Err(err).Msgf("error sending list container grpc request %s", fn)
w.WriteHeader(http.StatusInternalServerError)
return
}
if res.Status.Code != cs3rpc.Code_CODE_OK {
g.logger.Error().Err(err).Msgf("error calling grpc list container %s", fn)
w.WriteHeader(http.StatusInternalServerError)
return
}
files, err := formatDriveItems(res.Infos)
if err != nil {
g.logger.Error().Err(err).Msgf("error encoding response as json %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
render.Status(r, http.StatusOK)
render.JSON(w, r, &listResponse{Value: files})
}
func cs3ResourceToDriveItem(res *storageprovider.ResourceInfo) (*msgraph.DriveItem, error) {
size := new(int)
*size = int(res.Size) // uint64 -> int :boom:
name := strings.TrimPrefix(res.Path, "/home/")
lastModified := new(time.Time)
*lastModified = time.Unix(int64(res.Mtime.Seconds), int64(res.Mtime.Nanos))
driveItem := &msgraph.DriveItem{
BaseItem: msgraph.BaseItem{
Entity: msgraph.Entity{
Object: msgraph.Object{},
ID: &res.Id.OpaqueId,
},
Name: &name,
LastModifiedDateTime: lastModified,
ETag: &res.Etag,
},
Size: size,
}
if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_FILE {
driveItem.File = &msgraph.File{
MimeType: &res.MimeType,
}
}
if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER {
driveItem.Folder = &msgraph.Folder{
}
}
return driveItem, nil
}
func formatDriveItems(mds []*storageprovider.ResourceInfo) ([]*msgraph.DriveItem, error) {
responses := make([]*msgraph.DriveItem, 0, len(mds))
for i := range mds {
res, err := cs3ResourceToDriveItem(mds[i])
if err != nil {
return nil, err
}
responses = append(responses, res)
}
return responses, nil
}

View File

@@ -0,0 +1,75 @@
package errorcode
import (
"net/http"
"github.com/go-chi/render"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
)
// ErrorCode defines code as used in MS Graph - see https://docs.microsoft.com/en-us/graph/errors?context=graph%2Fapi%2F1.0&view=graph-rest-1.0
type ErrorCode int
const (
// AccessDenied defines the error if the caller doesn't have permission to perform the action.
AccessDenied ErrorCode = iota
// ActivityLimitReached defines the error if the app or user has been throttled.
ActivityLimitReached
// GeneralException defines the error if an unspecified error has occurred.
GeneralException
// InvalidRange defines the error if the specified byte range is invalid or unavailable.
InvalidRange
// InvalidRequest defines the error if the request is malformed or incorrect.
InvalidRequest
// ItemNotFound defines the error if the resource could not be found.
ItemNotFound
// MalwareDetected defines the error if malware was detected in the requested resource.
MalwareDetected
// NameAlreadyExists defines the error if the specified item name already exists.
NameAlreadyExists
// NotAllowed defines the error if the action is not allowed by the system.
NotAllowed
// NotSupported defines the error if the request is not supported by the system.
NotSupported
// ResourceModified defines the error if the resource being updated has changed since the caller last read it, usually an eTag mismatch.
ResourceModified
// ResyncRequired defines the error if the delta token is no longer valid, and the app must reset the sync state.
ResyncRequired
// ServiceNotAvailable defines the error if the service is not available. Try the request again after a delay. There may be a Retry-After header.
ServiceNotAvailable
// QuotaLimitReached the user has reached their quota limit.
QuotaLimitReached
// Unauthenticated the caller is not authenticated.
Unauthenticated
)
var errorCodes = [...]string{
"accessDenied",
"activityLimitReached",
"generalException",
"invalidRange",
"invalidRequest",
"itemNotFound",
"malwareDetected",
"nameAlreadyExists",
"notAllowed",
"notSupported",
"resourceModified",
"resyncRequired",
"serviceNotAvailable",
"quotaLimitReached",
"unauthenticated",
}
// Render writes an Graph ErrorObject to the response writer
func (e ErrorCode) Render(w http.ResponseWriter, r *http.Request, status int) {
resp := &msgraph.ErrorObject{
Code: e.String(),
}
render.Status(r, status)
render.JSON(w, r, resp)
}
func (e ErrorCode) String() string {
return errorCodes[e]
}

View File

@@ -0,0 +1,39 @@
package svc
import (
"net/http"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/go-chi/chi"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis/graph/pkg/config"
"github.com/owncloud/ocis/graph/pkg/cs3"
)
// Graph defines implements the business logic for Service.
type Graph struct {
config *config.Config
mux *chi.Mux
logger *log.Logger
}
// ServeHTTP implements the Service interface.
func (g Graph) ServeHTTP(w http.ResponseWriter, r *http.Request) {
g.mux.ServeHTTP(w, r)
}
// GetClient returns a gateway client to talk to reva
func (g Graph) GetClient() (gateway.GatewayAPIClient, error) {
return cs3.GetGatewayServiceClient(g.config.Reva.Address)
}
// The key type is unexported to prevent collisions with context keys defined in
// other packages.
type key int
const userIDKey key = 0
const groupIDKey key = 1
type listResponse struct {
Value interface{} `json:"value,omitempty"`
}

View File

@@ -0,0 +1,77 @@
package svc
import (
"context"
"fmt"
"net/http"
"github.com/owncloud/ocis/graph/pkg/service/v0/errorcode"
"github.com/go-chi/chi"
"github.com/go-chi/render"
"github.com/go-ldap/ldap/v3"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
)
// GroupCtx middleware is used to load an User object from
// the URL parameters passed through as the request. In case
// the User could not be found, we stop here and return a 404.
func (g Graph) GroupCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
groupID := chi.URLParam(r, "groupID")
if groupID == "" {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest)
return
}
filter := fmt.Sprintf("(entryuuid=%s)", groupID)
group, err := g.ldapGetSingleEntry(g.config.Ldap.BaseDNGroups, filter)
if err != nil {
g.logger.Info().Err(err).Msgf("Failed to read group %s", groupID)
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound)
return
}
ctx := context.WithValue(r.Context(), groupIDKey, group)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// GetGroups implements the Service interface.
func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) {
con, err := g.initLdap()
if err != nil {
g.logger.Error().Err(err).Msg("Failed to initialize ldap")
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError)
return
}
result, err := g.ldapSearch(con, "(objectclass=*)", g.config.Ldap.BaseDNGroups)
if err != nil {
g.logger.Error().Err(err).Msg("Failed search ldap with filter: '(objectclass=*)'")
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError)
return
}
var groups []*msgraph.Group
for _, group := range result.Entries {
groups = append(
groups,
createGroupModelFromLDAP(
group,
),
)
}
render.Status(r, http.StatusOK)
render.JSON(w, r, &listResponse{Value: groups})
}
// GetGroup implements the Service interface.
func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) {
group := r.Context().Value(groupIDKey).(*ldap.Entry)
render.Status(r, http.StatusOK)
render.JSON(w, r, createGroupModelFromLDAP(group))
}

View File

@@ -0,0 +1,40 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis/graph/pkg/metrics"
)
// NewInstrument returns a service that instruments metrics.
func NewInstrument(next Service, metrics *metrics.Metrics) Service {
return instrument{
next: next,
metrics: metrics,
}
}
type instrument struct {
next Service
metrics *metrics.Metrics
}
// ServeHTTP implements the Service interface.
func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) {
i.next.ServeHTTP(w, r)
}
// GetMe implements the Service interface.
func (i instrument) GetMe(w http.ResponseWriter, r *http.Request) {
i.next.GetMe(w, r)
}
// GetUsers implements the Service interface.
func (i instrument) GetUsers(w http.ResponseWriter, r *http.Request) {
i.next.GetUsers(w, r)
}
// GetUsers implements the Service interface.
func (i instrument) GetUser(w http.ResponseWriter, r *http.Request) {
i.next.GetUser(w, r)
}

View File

@@ -0,0 +1,94 @@
package svc
import (
"errors"
"github.com/go-ldap/ldap/v3"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
)
func (g Graph) ldapGetSingleEntry(baseDn string, filter string) (*ldap.Entry, error) {
conn, err := g.initLdap()
if err != nil {
return nil, err
}
result, err := g.ldapSearch(conn, filter, baseDn)
if err != nil {
return nil, err
}
if len(result.Entries) == 0 {
return nil, errors.New("resource not found")
}
return result.Entries[0], nil
}
func (g Graph) initLdap() (*ldap.Conn, error) {
g.logger.Info().Msgf("Dialing ldap %s://%s", g.config.Ldap.Network, g.config.Ldap.Address)
con, err := ldap.Dial(g.config.Ldap.Network, g.config.Ldap.Address)
if err != nil {
return nil, err
}
if err := con.Bind(g.config.Ldap.UserName, g.config.Ldap.Password); err != nil {
return nil, err
}
return con, nil
}
func (g Graph) ldapSearch(con *ldap.Conn, filter string, baseDN string) (*ldap.SearchResult, error) {
search := ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
filter,
[]string{"dn",
"uid",
"givenname",
"mail",
"displayname",
"entryuuid",
"sn",
"cn",
},
nil,
)
return con.Search(search)
}
func createUserModelFromLDAP(entry *ldap.Entry) *msgraph.User {
displayName := entry.GetAttributeValue("displayname")
givenName := entry.GetAttributeValue("givenname")
mail := entry.GetAttributeValue("mail")
surName := entry.GetAttributeValue("sn")
id := entry.GetAttributeValue("entryuuid")
return &msgraph.User{
DisplayName: &displayName,
GivenName: &givenName,
Surname: &surName,
Mail: &mail,
DirectoryObject: msgraph.DirectoryObject{
Entity: msgraph.Entity{
ID: &id,
},
},
}
}
func createGroupModelFromLDAP(entry *ldap.Entry) *msgraph.Group {
id := entry.GetAttributeValue("entryuuid")
displayName := entry.GetAttributeValue("cn")
return &msgraph.Group{
DisplayName: &displayName,
DirectoryObject: msgraph.DirectoryObject{
Entity: msgraph.Entity{
ID: &id,
},
},
}
}

View File

@@ -0,0 +1,40 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis-pkg/v2/log"
)
// NewLogging returns a service that logs messages.
func NewLogging(next Service, logger log.Logger) Service {
return logging{
next: next,
logger: logger,
}
}
type logging struct {
next Service
logger log.Logger
}
// ServeHTTP implements the Service interface.
func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) {
l.next.ServeHTTP(w, r)
}
// GetMe implements the Service interface.
func (l logging) GetMe(w http.ResponseWriter, r *http.Request) {
l.next.GetMe(w, r)
}
// GetUsers implements the Service interface.
func (l logging) GetUsers(w http.ResponseWriter, r *http.Request) {
l.next.GetUsers(w, r)
}
// GetUser implements the Service interface.
func (l logging) GetUser(w http.ResponseWriter, r *http.Request) {
l.next.GetUser(w, r)
}

View File

@@ -0,0 +1,50 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis/graph/pkg/config"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Config *config.Config
Middleware []func(http.Handler) http.Handler
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Middleware provides a function to set the middleware option.
func Middleware(val ...func(http.Handler) http.Handler) Option {
return func(o *Options) {
o.Middleware = val
}
}

View File

@@ -0,0 +1,56 @@
package svc
import (
"net/http"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)
// Service defines the extension handlers.
type Service interface {
ServeHTTP(http.ResponseWriter, *http.Request)
GetMe(http.ResponseWriter, *http.Request)
GetUsers(http.ResponseWriter, *http.Request)
GetUser(http.ResponseWriter, *http.Request)
}
// NewService returns a service implementation for Service.
func NewService(opts ...Option) Service {
options := newOptions(opts...)
m := chi.NewMux()
m.Use(options.Middleware...)
svc := Graph{
config: options.Config,
mux: m,
logger: &options.Logger,
}
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
r.Use(middleware.StripSlashes)
r.Route("/v1.0", func(r chi.Router) {
r.Route("/me", func(r chi.Router) {
r.Get("/", svc.GetMe)
r.Get("/drive/root/children", svc.GetRootDriveChildren)
})
r.Route("/users", func(r chi.Router) {
r.Get("/", svc.GetUsers)
r.Route("/{userID}", func(r chi.Router) {
r.Use(svc.UserCtx)
r.Get("/", svc.GetUser)
})
})
r.Route("/groups", func(r chi.Router) {
r.Get("/", svc.GetGroups)
r.Route("/{groupID}", func(r chi.Router) {
r.Use(svc.GroupCtx)
r.Get("/", svc.GetGroup)
})
})
})
})
return svc
}

View File

@@ -0,0 +1,36 @@
package svc
import (
"net/http"
)
// NewTracing returns a service that instruments traces.
func NewTracing(next Service) Service {
return tracing{
next: next,
}
}
type tracing struct {
next Service
}
// ServeHTTP implements the Service interface.
func (t tracing) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t.next.ServeHTTP(w, r)
}
// GetMe implements the Service interface.
func (t tracing) GetMe(w http.ResponseWriter, r *http.Request) {
t.next.GetMe(w, r)
}
// GetUsers implements the Service interface.
func (t tracing) GetUsers(w http.ResponseWriter, r *http.Request) {
t.next.GetUsers(w, r)
}
// GetUser implements the Service interface.
func (t tracing) GetUser(w http.ResponseWriter, r *http.Request) {
t.next.GetUser(w, r)
}

View File

@@ -0,0 +1,100 @@
package svc
import (
"context"
"fmt"
"net/http"
"github.com/owncloud/ocis/graph/pkg/service/v0/errorcode"
"github.com/go-chi/chi"
"github.com/go-chi/render"
"github.com/go-ldap/ldap/v3"
"github.com/owncloud/ocis-pkg/v2/oidc"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
)
// UserCtx middleware is used to load an User object from
// the URL parameters passed through as the request. In case
// the User could not be found, we stop here and return a 404.
func (g Graph) UserCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var user *ldap.Entry
var err error
userID := chi.URLParam(r, "userID")
if userID == "" {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest)
return
}
filter := fmt.Sprintf("(entryuuid=%s)", userID)
user, err = g.ldapGetSingleEntry(g.config.Ldap.BaseDNUsers, filter)
if err != nil {
g.logger.Info().Err(err).Msgf("Failed to read user %s", userID)
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound)
return
}
ctx := context.WithValue(r.Context(), userIDKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// GetMe implements the Service interface.
func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) {
claims := oidc.FromContext(r.Context())
g.logger.Info().Interface("Claims", claims).Msg("Claims in /me")
filter := fmt.Sprintf("(uid=%s)", claims.PreferredUsername)
user, err := g.ldapGetSingleEntry(g.config.Ldap.BaseDNUsers, filter)
if err != nil {
g.logger.Info().Err(err).Msgf("Failed to read user %s", claims.PreferredUsername)
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound)
return
}
me := createUserModelFromLDAP(user)
render.Status(r, http.StatusOK)
render.JSON(w, r, me)
}
// GetUsers implements the Service interface.
func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
con, err := g.initLdap()
if err != nil {
g.logger.Error().Err(err).Msg("Failed to initialize ldap")
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError)
return
}
result, err := g.ldapSearch(con, "(objectclass=*)", g.config.Ldap.BaseDNUsers)
if err != nil {
g.logger.Error().Err(err).Msg("Failed search ldap with filter: '(objectclass=*)'")
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError)
return
}
var users []*msgraph.User
for _, user := range result.Entries {
users = append(
users,
createUserModelFromLDAP(
user,
),
)
}
render.Status(r, http.StatusOK)
render.JSON(w, r, &listResponse{Value: users})
}
// GetUser implements the Service interface.
func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(userIDKey).(*ldap.Entry)
render.Status(r, http.StatusOK)
render.JSON(w, r, createUserModelFromLDAP(user))
}

View File

@@ -0,0 +1,19 @@
package version
import (
"time"
)
var (
// String gets defined by the build system.
String = "0.0.0"
// Date indicates the build date.
Date = "00000000"
)
// Compiled returns the compile time of this service.
func Compiled() time.Time {
t, _ := time.Parse("20060102", Date)
return t
}

2
graph/reflex.conf Normal file
View File

@@ -0,0 +1,2 @@
# backend
-r '^(cmd|pkg)/.*\.go$' -R '^node_modules/' -s -- sh -c 'make bin/ocis-graph-debug && bin/ocis-graph-debug --log-level debug server --debug-pprof --debug-zpages --oidc-endpoint="https://deepdiver" --oidc-insecure=1'

View File

@@ -0,0 +1,76 @@
---
title: "Configuration"
date: "{{ date "2006-01-02T15:04:05-0700" now }}"
weight: 20
geekdocRepo: https://github.com/owncloud/ocis
geekdocEditPath: edit/master/graph/templates
geekdocFilePath: CONFIGURATION.tmpl
---
{{- define "options"}}
{{ $fnName := (last . ).Flags -}}
{{ range $opt := first . }}{{ with list $fnName $opt -}}
{{ $o := last . -}}
{{ if eq $o.FnName $fnName -}}
-{{ $o.Name }} | {{ range $i, $e := $o.Env }} {{ if $i }}, {{ end }}${{ $e }}{{ end }}
: {{ $o.Usage }}. {{- if $o.Default }} Default: `{{ $o.Default }}`.{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end }}
{{`{{< toc >}}`}}
## Configuration
### Configuration using config files
Out of the box extensions will attempt to read configuration details from:
```console
/etc/ocis
$HOME/.ocis
./config
```
For this configuration to be picked up, have a look at your extension `root` command and look for which default config name it has assigned. *i.e: ocis-proxy reads `proxy.json | yaml | toml ...`*.
So far we support the file formats `JSON` and `YAML`, if you want to get a full example configuration just take a look at [our repository](https://github.com/owncloud/ocis/tree/master/graph/config), there you can always see the latest configuration format. These example configurations include all available options and the default values. The configuration file will be automatically loaded if it's placed at `/etc/ocis/ocis.yml`, `${HOME}/.ocis/ocis.yml` or `$(pwd)/config/ocis.yml`.
### Environment variables
If you prefer to configure the service with environment variables you can see the available variables below.
If multiple variables are listed for one option, they are in order of precedence. This means the leftmost variable will always win if given.
### Commandline flags
If you prefer to configure the service with commandline flags you can see the available variables below. Command line flags are only working when calling the subcommand directly.
{{ $options := .Options -}}
{{ range $com := .Commands }}{{ with (list $options $com) -}}
{{ $c := last . -}}
{{ if eq $c.Name "graph" -}}
## Root Command
{{ $c.Usage }}
Usage: `graph [global options] command [command options] [arguments...]`
{{ template "options" . -}}
## Sub Commands
{{ end -}}
{{ end -}}
{{ end -}}
{{- range $com := .Commands }}{{ with (list $options $com) -}}
{{- $c := last . }}
{{- if ne $c.Name "graph" -}}
### graph {{ $c.Name }}
{{ $c.Usage }}
Usage: `graph {{ $c.Name }} [command options] [arguments...]`
{{ template "options" . }}
{{- end -}}
{{- end -}}
{{- end -}}

View File

@@ -20,6 +20,7 @@ require (
github.com/owncloud/ocis-hello v0.1.0-alpha1.0.20200828085053-37fcf3c8f853
github.com/owncloud/ocis/accounts v0.5.3-0.20201103104733-ff2c41028d9b
github.com/owncloud/ocis/glauth v0.0.0-00010101000000-000000000000
github.com/owncloud/ocis/graph v0.0.0-20210204104632-f55a8092cb1f // indirect
github.com/owncloud/ocis/idp v0.0.0-00010101000000-000000000000
github.com/owncloud/ocis/ocis-pkg v0.1.0
github.com/owncloud/ocis/ocs v0.0.0-00010101000000-000000000000
@@ -56,5 +57,6 @@ replace (
github.com/owncloud/ocis/thumbnails => ../thumbnails
github.com/owncloud/ocis/web => ../web
github.com/owncloud/ocis/webdav => ../webdav
github.com/owncloud/ocis/graph => ../graph
google.golang.org/grpc => google.golang.org/grpc v1.26.0
)

View File

@@ -272,6 +272,7 @@ github.com/cs3org/cato v0.0.0-20200626150132-28a40e643719/go.mod h1:XJEZ3/EQuI3B
github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4=
github.com/cs3org/go-cs3apis v0.0.0-20191128165347-19746c015c83/go.mod h1:IsVGyZrOLUQD48JIhlM/xb3Vz6He5o2+W0ZTfUGY+IU=
github.com/cs3org/go-cs3apis v0.0.0-20200730121022-c4f3d4f7ddfd/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/cs3org/go-cs3apis v0.0.0-20200810113633-b00aca449666/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/cs3org/go-cs3apis v0.0.0-20210104105209-0d3ecb3453dc h1:vHFqu+Gb/iOKYFy2KswpwIG3G6zRMudRn+rQ2bg3TPE=
github.com/cs3org/go-cs3apis v0.0.0-20210104105209-0d3ecb3453dc/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/cs3org/reva v0.0.2-0.20200115110931-4c7513415ec5/go.mod h1:Hk3eCcdhtv4eIhKvRK736fQuOyS1HuHnUcz0Dq6NK1A=
@@ -1244,6 +1245,7 @@ github.com/owncloud/ocis-graph-explorer v0.0.0-20200210111049-017eeb40dc0c/go.mo
github.com/owncloud/ocis-hello v0.1.0-alpha1.0.20200828085053-37fcf3c8f853 h1:ei0C5Wmppw+9oiSB0XpAdwymD8+ZnmewNdVVzYE3UvQ=
github.com/owncloud/ocis-hello v0.1.0-alpha1.0.20200828085053-37fcf3c8f853/go.mod h1:vnpYlDkhVoiuUAuTnY4Ajz2d5Alz0c/49AxptOLTpYA=
github.com/owncloud/ocis-pkg/v2 v2.0.1/go.mod h1:7bVnn3VUaqdmvpMkXF0QVEF1fRugs35hSkuVTAq9yjk=
github.com/owncloud/ocis-pkg/v2 v2.2.1/go.mod h1:MXv7QzsYsu4YWuyJxhq1kLLmJa/r5gbqHe1FXulMHaw=
github.com/owncloud/ocis-pkg/v2 v2.4.0 h1:/3ZOd4txtwjiNKJA9iLT9BjrJw5YgHSX13fQR4BYfGY=
github.com/owncloud/ocis-pkg/v2 v2.4.0/go.mod h1:FSzIvhx9HcZcq4jgNaDowNvM7PTX/XCyoMvyfzidUpE=
github.com/owncloud/ocis-settings v0.3.2-0.20200827193534-8caf098e6537/go.mod h1:vRge9QDkOsc6j76gPBmZs1Z5uOPrV4DIkZCgZCEFwBA=
@@ -1342,6 +1344,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq
github.com/restic/calens v0.1.0/go.mod h1:u67f5msOjCTDYNzOf/NoAUSdmXP03YXPCwIQLYADy5M=
github.com/restic/calens v0.2.0 h1:LVNAtmFc+Pb4ODX66qdX1T3Di1P0OTLyUsVyvM/xD7E=
github.com/restic/calens v0.2.0/go.mod h1:UXwyAKS4wsgUZGEc7NrzzygJbLsQZIo3wl+62Q1wvmU=
github.com/rickb777/date v1.12.4/go.mod h1:xP0eo/I5qmUt97yRGClHZfyLZ3ikMw6v6SU5MOGZTE0=
github.com/rickb777/plural v1.2.0/go.mod h1:UdpyWFCGbo3mvK3f/PfZOAOrkjzJlYN/sD46XNWJ+Es=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.0.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -1543,6 +1547,8 @@ github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63M
github.com/yaegashi/msgraph.go v0.0.0-20191104022859-3f9096c750b2/go.mod h1:tso14hwzqX4VbnWTNsxiL0DvMb2OwbGISFA7jDibdWc=
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc h1:ejaC8rvIvCWmsaFrvmGOxhBuMxxhBB1xRshuM98XQ7M=
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc/go.mod h1:tso14hwzqX4VbnWTNsxiL0DvMb2OwbGISFA7jDibdWc=
github.com/yaegashi/msgraph.go v0.1.4/go.mod h1:vgeYhHa5skJt/3lTyjGXThTZhwbhRnGo6uUxzoJIGME=
github.com/yaegashi/wtz.go v0.0.2/go.mod h1:nOLA5QXsmdkRxBkP5tljhua13ADHCKirLBrzPf4PEJc=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1941,7 +1947,9 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200529172331-a64b76657301/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200721223218-6123e77877b2/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200811215021-48a8ffc5b207 h1:8Kg+JssU1jBZs8GIrL5pl4nVyaqyyhdmHAR4D1zGErg=
golang.org/x/tools v0.0.0-20200811215021-48a8ffc5b207/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=

View File

@@ -7,6 +7,7 @@ import (
accounts "github.com/owncloud/ocis/accounts/pkg/config"
glauth "github.com/owncloud/ocis/glauth/pkg/config"
idp "github.com/owncloud/ocis/idp/pkg/config"
pman "github.com/owncloud/ocis/ocis/pkg/runtime/config"
ocs "github.com/owncloud/ocis/ocs/pkg/config"
onlyoffice "github.com/owncloud/ocis/onlyoffice/pkg/config"
proxy "github.com/owncloud/ocis/proxy/pkg/config"
@@ -16,7 +17,6 @@ import (
thumbnails "github.com/owncloud/ocis/thumbnails/pkg/config"
web "github.com/owncloud/ocis/web/pkg/config"
webdav "github.com/owncloud/ocis/webdav/pkg/config"
pman "github.com/refs/pman/pkg/config"
)
// Log defines the available logging configuration.

View File

@@ -1067,6 +1067,7 @@ github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE=
github.com/ory/x v0.0.162 h1:xE/UBmmMlInTvlgGXUyo+VeZAcWU5gyWb/xh6jmBWsI=
github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203 h1:sXVTQ94GWeiBhlgN0kSbcyXQNmwzPuuhWyObxgTu/xE=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203/go.mod h1:jip86t4OVURJTf8CM/0e2qcji/Y4NG3l2lR8kex4JWw=
github.com/owncloud/ocis/storage v0.0.0-20201015120921-38358ba4d4df h1:PhRLD+WTGIfQ1T4MqBabp6/1Q8H/iwxjlygh6xzao0A=
github.com/owncloud/ocis/storage v0.0.0-20201015120921-38358ba4d4df/go.mod h1:s9kJvxtBlHEi5qc1TuPAdz2bprk9yGFe+FSOeC76Pbs=

View File

@@ -1032,6 +1032,7 @@ github.com/ory/x v0.0.93/go.mod h1:lfcTaGXpTZs7IEQAW00r9EtTCOxD//SiP5uWtNiz31g=
github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE=
github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203 h1:sXVTQ94GWeiBhlgN0kSbcyXQNmwzPuuhWyObxgTu/xE=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203/go.mod h1:jip86t4OVURJTf8CM/0e2qcji/Y4NG3l2lR8kex4JWw=
github.com/owncloud/ocis-pkg/v2 v2.4.0 h1:/3ZOd4txtwjiNKJA9iLT9BjrJw5YgHSX13fQR4BYfGY=
github.com/owncloud/ocis-pkg/v2 v2.4.0/go.mod h1:FSzIvhx9HcZcq4jgNaDowNvM7PTX/XCyoMvyfzidUpE=

View File

@@ -1104,6 +1104,7 @@ github.com/ory/x v0.0.93/go.mod h1:lfcTaGXpTZs7IEQAW00r9EtTCOxD//SiP5uWtNiz31g=
github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE=
github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203 h1:sXVTQ94GWeiBhlgN0kSbcyXQNmwzPuuhWyObxgTu/xE=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203/go.mod h1:jip86t4OVURJTf8CM/0e2qcji/Y4NG3l2lR8kex4JWw=
github.com/owncloud/ocis-pkg/v2 v2.4.0/go.mod h1:FSzIvhx9HcZcq4jgNaDowNvM7PTX/XCyoMvyfzidUpE=
github.com/owncloud/ocis-pkg/v2 v2.4.1-0.20200902134813-1e87c6173ada/go.mod h1:WdcVM54z0X7aQzS8eyGl7S5sjEMVBtLpfpzsPX3Z+Pw=

View File

@@ -1062,6 +1062,7 @@ github.com/ory/x v0.0.93/go.mod h1:lfcTaGXpTZs7IEQAW00r9EtTCOxD//SiP5uWtNiz31g=
github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE=
github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203 h1:sXVTQ94GWeiBhlgN0kSbcyXQNmwzPuuhWyObxgTu/xE=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203/go.mod h1:jip86t4OVURJTf8CM/0e2qcji/Y4NG3l2lR8kex4JWw=
github.com/owncloud/ocis-pkg/v2 v2.4.0/go.mod h1:FSzIvhx9HcZcq4jgNaDowNvM7PTX/XCyoMvyfzidUpE=
github.com/owncloud/ocis-pkg/v2 v2.4.1-0.20200902134813-1e87c6173ada/go.mod h1:WdcVM54z0X7aQzS8eyGl7S5sjEMVBtLpfpzsPX3Z+Pw=

View File

@@ -1049,6 +1049,7 @@ github.com/ory/x v0.0.93/go.mod h1:lfcTaGXpTZs7IEQAW00r9EtTCOxD//SiP5uWtNiz31g=
github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE=
github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203 h1:sXVTQ94GWeiBhlgN0kSbcyXQNmwzPuuhWyObxgTu/xE=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203/go.mod h1:jip86t4OVURJTf8CM/0e2qcji/Y4NG3l2lR8kex4JWw=
github.com/owncloud/ocis-pkg/v2 v2.4.0/go.mod h1:FSzIvhx9HcZcq4jgNaDowNvM7PTX/XCyoMvyfzidUpE=
github.com/owncloud/ocis-pkg/v2 v2.4.1-0.20200902134813-1e87c6173ada/go.mod h1:WdcVM54z0X7aQzS8eyGl7S5sjEMVBtLpfpzsPX3Z+Pw=

View File

@@ -704,6 +704,7 @@ github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -1053,6 +1054,7 @@ github.com/ory/x v0.0.93/go.mod h1:lfcTaGXpTZs7IEQAW00r9EtTCOxD//SiP5uWtNiz31g=
github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE=
github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec=
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203 h1:sXVTQ94GWeiBhlgN0kSbcyXQNmwzPuuhWyObxgTu/xE=
github.com/owncloud/flaex v0.0.0-20200411150708-dce59891a203/go.mod h1:jip86t4OVURJTf8CM/0e2qcji/Y4NG3l2lR8kex4JWw=
github.com/owncloud/ocis-pkg/v2 v2.4.0/go.mod h1:FSzIvhx9HcZcq4jgNaDowNvM7PTX/XCyoMvyfzidUpE=
github.com/owncloud/ocis-pkg/v2 v2.4.1-0.20200902134813-1e87c6173ada/go.mod h1:WdcVM54z0X7aQzS8eyGl7S5sjEMVBtLpfpzsPX3Z+Pw=