Skip to content

Commit

Permalink
Source available metrics (mattermost#24879)
Browse files Browse the repository at this point in the history
* Expose metrics under a source available license

* do not assume Cluster()

* allow metrics if licensed or dev

* temporary vet override

* simplify BULID_TAGS handling

* auto clean old imports.go

* fix license listener

* e2e test metrics & license semantics

* update from enterprise

* switch back to mattermost-govet/v2@new now

* update metrics from upstream

* Update license_spec.js

Co-authored-by: Saturnino Abril <[email protected]>

* Update license_spec.js

Co-authored-by: Saturnino Abril <[email protected]>

* Update e2e-tests/cypress/tests/integration/channels/enterprise/metrics/license_spec.js

Co-authored-by: Saturnino Abril <[email protected]>

* Update e2e-tests/cypress/tests/integration/channels/enterprise/metrics/license_spec.js

* split up specs

* require/delete license earlier in e2e test

* expanded expect to debug failures

* more logging

* Revert "more logging"

This reverts commit 0bc513f.

* e2e: try deleting license first

* update from enterprise

* toggleMetricsOn to work around license delete

* eslint

* ensure admin before deleting license

* update from enterprise

* updates from enterprise

* fix cypress logging

* temp: log at DEBUG for Cypress tests

---------

Co-authored-by: Saturnino Abril <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>
  • Loading branch information
3 people authored Jan 8, 2024
1 parent 6184c36 commit b05093d
Show file tree
Hide file tree
Showing 22 changed files with 1,984 additions and 65 deletions.
1 change: 1 addition & 0 deletions LICENSE.enterprise
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

export const checkMetrics = (expectedStatusCode) => {
cy.apiGetConfig().then(({config}) => {
const baseURL = new URL(Cypress.config('baseUrl'));
baseURL.port = config.MetricsSettings.ListenAddress.replace(/^.*:/, '');
baseURL.pathname = '/metrics';

cy.log({name: 'Metrics License', message: `Checking metrics at ${baseURL.toString()}`});
cy.request({
headers: {'X-Requested-With': 'XMLHttpRequest'},
url: baseURL.toString(),
method: 'GET',
failOnStatusCode: false,
}).then((response) => {
expect(response.headers['Content-Type'], 'should not hit webapp').not.to.equal('text/html');
expect(response.status, 'should match expected status code').to.equal(expectedStatusCode);
});
});
};

// toggleMetricsOn turns metrics off and back on, forcing it to be tested against the current
// license. When, in the future, the product detects license removal and does this automatically,
// this helper won't be required.
export const toggleMetricsOn = () => {
cy.apiUpdateConfig({
MetricsSettings: {
Enable: false,
},
});
cy.apiUpdateConfig({
MetricsSettings: {
Enable: true,
},
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************

// Stage: @prod
// Group: @channels @enterprise @metrics @not_cloud @license_removal

import {checkMetrics, toggleMetricsOn} from './helper';

describe('Metrics > No license', () => {
before(() => {
cy.shouldNotRunOnCloudEdition();
cy.apiAdminLogin();
cy.apiDeleteLicense();
toggleMetricsOn();
});

it('should enable metrics in BUILD_NUMBER == dev environments', () => {
cy.apiGetConfig(true).then(({config}) => {
if (config.BuildNumber !== 'dev') {
Cypress.log({name: 'Metrics License', message: `Skipping test since BUILD_NUMBER = ${config.BuildNumber}`});
return;
}

checkMetrics(200);
});
});

it('should disable metrics in BUILD_NUMBER != dev environments', () => {
cy.apiGetConfig(true).then(({config}) => {
if (config.BuildNumber === 'dev') {
Cypress.log({name: 'Metrics License', message: `Skipping test since BUILD_NUMBER = ${config.BuildNumber}`});
return;
}

checkMetrics(404);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************

// Stage: @prod
// Group: @channels @enterprise @metrics @not_cloud

import {checkMetrics, toggleMetricsOn} from './helper';

describe('Metrics > License', () => {
before(() => {
cy.shouldNotRunOnCloudEdition();
cy.apiRequireLicense();
toggleMetricsOn();
});

it('should enable metrics in BUILD_NUMBER == dev environments', () => {
cy.apiGetConfig(true).then(({config}) => {
if (config.BuildNumber !== 'dev') {
Cypress.log({name: 'Metrics License', message: `Skipping test since BUILD_NUMBER = ${config.BuildNumber}`});
return;
}

checkMetrics(200);
});
});

it('should enable metrics in BUILD_NUMBER != dev environments', () => {
cy.apiGetConfig(true).then(({config}) => {
if (config.BuildNumber === 'dev') {
Cypress.log({name: 'Metrics License', message: `Skipping test since BUILD_NUMBER = ${config.BuildNumber}`});
return;
}

checkMetrics(200);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
"ConsoleJson": true,
"EnableColor": false,
"EnableFile": true,
"FileLevel": "INFO",
"FileLevel": "DEBUG",
"FileJson": true,
"FileLocation": "",
"EnableWebhookDebugging": true,
Expand Down
5 changes: 0 additions & 5 deletions server/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ config/active.dat
config/logging.json
/plugins

# Enterprise & products imports files
channels/imports/imports.go

# go.work file
go.work
go.work.sum
Expand Down Expand Up @@ -104,8 +101,6 @@ api/data/*
api4/data/*
app/data/*

/enterprise

cover.out
ecover.out
mmctlcover.out
Expand Down
48 changes: 24 additions & 24 deletions server/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ IS_CI ?= false
BUILD_NUMBER ?= $(BUILD_NUMBER:)
BUILD_DATE = $(shell date -u)
BUILD_HASH = $(shell git rev-parse HEAD)
BUILD_TAGS =


# Docker
Expand Down Expand Up @@ -73,6 +74,7 @@ ifneq ($(wildcard $(BUILD_ENTERPRISE_DIR)/.),)
BUILD_ENTERPRISE_READY = true
BUILD_TYPE_NAME = enterprise
BUILD_HASH_ENTERPRISE = $(shell cd $(BUILD_ENTERPRISE_DIR) && git rev-parse HEAD)
BUILD_TAGS += enterprise
else
BUILD_ENTERPRISE_READY = false
BUILD_TYPE_NAME = team
Expand All @@ -82,6 +84,16 @@ else
BUILD_TYPE_NAME = team
endif

# Clean up the old means of importing enterprise source, if it exists
ifneq ($(wildcard channels/imports/imports.go),)
IGNORE := $(shell rm -f channels/imports/imports.go)
endif

# Source available, already included with enterprise but also available during development.
ifeq ($(BUILD_NUMBER),dev)
BUILD_TAGS += sourceavailable
endif

# Webapp
BUILD_WEBAPP_DIR ?= ../webapp

Expand Down Expand Up @@ -156,16 +168,6 @@ PLUGIN_PACKAGES += mattermost-plugin-zoom-v1.6.2
PLUGIN_PACKAGES += mattermost-plugin-apps-v1.2.2
PLUGIN_PACKAGES += focalboard-v7.11.4

# Prepares the enterprise build if exists. The IGNORE stuff is a hack to get the Makefile to execute the commands outside a target
ifeq ($(BUILD_ENTERPRISE_READY),true)
IGNORE:=$(shell echo Enterprise build selected, preparing)
IGNORE:=$(shell rm -f channels/imports/imports.go)
IGNORE:=$(shell cp $(BUILD_ENTERPRISE_DIR)/imports/imports.go channels/imports/)
IGNORE:=$(shell rm -f enterprise)
else
IGNORE:=$(shell rm -f channels/imports/imports.go)
endif

EE_PACKAGES=$(shell $(GO) list $(BUILD_ENTERPRISE_DIR)/...)

ifeq ($(BUILD_ENTERPRISE_READY),true)
Expand Down Expand Up @@ -422,14 +424,10 @@ test-compile: gotestsum ## Compile tests.
done

modules-tidy:
@if [ -f "channels/imports/imports.go" ]; then \
mv channels/imports/imports.go channels/imports/imports.go.orig; \
fi;
mv enterprise/external_imports.go enterprise/external_imports.go.orig
-$(GO) mod tidy
-cd public && $(GO) mod tidy
@if [ -f "channels/imports/imports.go.orig" ]; then \
mv channels/imports/imports.go.orig channels/imports/imports.go; \
fi;
mv enterprise/external_imports.go.orig enterprise/external_imports.go

test-server-pre: check-prereqs-enterprise start-docker gotestsum ## Runs tests.
ifeq ($(BUILD_ENTERPRISE_READY),true)
Expand Down Expand Up @@ -580,7 +578,7 @@ run-server: setup-go-work prepackaged-binaries validate-go-version start-docker
@echo Running mattermost for development

mkdir -p $(BUILD_WEBAPP_DIR)/channels/dist/files
$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' $(PLATFORM_FILES) $(RUN_IN_BACKGROUND)
$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' $(PLATFORM_FILES) $(RUN_IN_BACKGROUND)

debug-server: start-docker ## Compile and start server using delve.
mkdir -p $(BUILD_WEBAPP_DIR)/channels/dist/files
Expand All @@ -589,7 +587,8 @@ debug-server: start-docker ## Compile and start server using delve.
-X \"github.com/mattermost/mattermost/server/public/model.BuildDate=$(BUILD_DATE)\"\
-X github.com/mattermost/mattermost/server/public/model.BuildHash=$(BUILD_HASH)\
-X github.com/mattermost/mattermost/server/public/model.BuildHashEnterprise=$(BUILD_HASH_ENTERPRISE)\
-X github.com/mattermost/mattermost/server/public/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)'"
-X github.com/mattermost/mattermost/server/public/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)'\
-tags '$(BUILD_TAGS)'"

debug-server-headless: start-docker ## Debug server from within an IDE like VSCode or IntelliJ.
mkdir -p $(BUILD_WEBAPP_DIR)/channels/dist/files
Expand All @@ -598,13 +597,14 @@ debug-server-headless: start-docker ## Debug server from within an IDE like VSCo
-X \"github.com/mattermost/mattermost/server/public/model.BuildDate=$(BUILD_DATE)\"\
-X github.com/mattermost/mattermost/server/public/model.BuildHash=$(BUILD_HASH)\
-X github.com/mattermost/mattermost/server/public/model.BuildHashEnterprise=$(BUILD_HASH_ENTERPRISE)\
-X github.com/mattermost/mattermost/server/public/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)'"
-X github.com/mattermost/mattermost/server/public/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)'\
-tags '$(BUILD_TAGS)'"

run-cli: start-docker ## Runs CLI.
@echo Running mattermost for development
@echo Example should be like 'make ARGS="-version" run-cli'

$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' $(PLATFORM_FILES) ${ARGS}
$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' $(PLATFORM_FILES) ${ARGS}

run-client: client ## Runs the webapp.
@echo Running mattermost client for development
Expand Down Expand Up @@ -664,7 +664,7 @@ restart-client: | stop-client run-client ## Restarts the webapp.

run-job-server: ## Runs the background job server.
@echo Running job server for development
$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' $(PLATFORM_FILES) jobserver &
$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' $(PLATFORM_FILES) jobserver &

config-ldap: ## Configures LDAP.
@echo Setting up configuration for local LDAP
Expand Down Expand Up @@ -730,8 +730,8 @@ update-dependencies: ## Uses go get -u to update all the dependencies while hold
@echo Updating Dependencies

ifeq ($(BUILD_ENTERPRISE_READY),true)
@echo Enterprise repository detected, temporarily removing imports.go
rm -f channels/imports/imports.go
@echo Enterprise repository detected, temporarily removing external_imports.go
mv enterprise/external_imports.go enterprise/external_imports.go.orig
endif

# Update all dependencies (does not update across major versions)
Expand All @@ -741,7 +741,7 @@ endif
$(GO) mod tidy

ifeq ($(BUILD_ENTERPRISE_READY),true)
cp $(BUILD_ENTERPRISE_DIR)/imports/imports.go channels/imports/
mv enterprise/external_imports.go.orig enterprise/external_imports.go
endif

vet: ## Run mattermost go vet specific checks
Expand Down
40 changes: 20 additions & 20 deletions server/build/release.mk
Original file line number Diff line number Diff line change
Expand Up @@ -5,85 +5,85 @@ build-linux: build-linux-amd64 build-linux-arm64
build-linux-amd64:
@echo Build Linux amd64
ifeq ($(BUILDER_GOOS_GOARCH),"linux_amd64")
env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
else
mkdir -p $(GOBIN)/linux_amd64
env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN)/linux_amd64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN)/linux_amd64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
endif

build-linux-arm64:
@echo Build Linux arm64
ifeq ($(BUILDER_GOOS_GOARCH),"linux_arm64")
env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
else
mkdir -p $(GOBIN)/linux_arm64
env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN)/linux_arm64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN)/linux_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
endif

build-osx:
@echo Build OSX amd64
ifeq ($(BUILDER_GOOS_GOARCH),"darwin_amd64")
env GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
env GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
else
mkdir -p $(GOBIN)/darwin_amd64
env GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOBIN)/darwin_amd64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
env GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOBIN)/darwin_amd64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
endif
@echo Build OSX arm64
ifeq ($(BUILDER_GOOS_GOARCH),"darwin_arm64")
env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
else
mkdir -p $(GOBIN)/darwin_arm64
env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN)/darwin_arm64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN)/darwin_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
endif

build-windows:
@echo Build Windows amd64
ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64")
env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
else
mkdir -p $(GOBIN)/windows_amd64
env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN)/windows_amd64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN)/windows_amd64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
endif

build-cmd-linux:
@echo Build CMD Linux amd64
ifeq ($(BUILDER_GOOS_GOARCH),"linux_amd64")
env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
else
mkdir -p $(GOBIN)/linux_amd64
env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN)/linux_amd64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN)/linux_amd64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
endif
@echo Build CMD Linux arm64
ifeq ($(BUILDER_GOOS_GOARCH),"linux_arm64")
env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
else
mkdir -p $(GOBIN)/linux_arm64
env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN)/linux_arm64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN)/linux_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
endif

build-cmd-osx:
@echo Build CMD OSX amd64
ifeq ($(BUILDER_GOOS_GOARCH),"darwin_amd64")
env GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
env GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
else
mkdir -p $(GOBIN)/darwin_amd64
env GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOBIN)/darwin_amd64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
env GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOBIN)/darwin_amd64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
endif
@echo Build CMD OSX arm64
ifeq ($(BUILDER_GOOS_GOARCH),"darwin_arm64")
env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
else
mkdir -p $(GOBIN)/darwin_arm64
env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN)/darwin_arm64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN)/darwin_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
endif

build-cmd-windows:
@echo Build CMD Windows amd64
ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64")
env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
else
mkdir -p $(GOBIN)/windows_amd64
env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN)/windows_amd64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN)/windows_amd64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
endif

build: setup-go-work build-client build-linux build-windows build-osx
Expand Down
Loading

0 comments on commit b05093d

Please sign in to comment.