Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Ijaz Ahmad
ksonnet
Commits
de033c2c
Commit
de033c2c
authored
Aug 13, 2018
by
Oren Shomron
Browse files
Add --tls-skip-verify global option affecting outgoing HTTP requests
Signed-off-by:
Oren Shomron
<
shomron@gmail.com
>
parent
746561a9
Changes
63
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
137 additions
and
57 deletions
+137
-57
pkg/actions/actions.go
pkg/actions/actions.go
+38
-0
pkg/actions/pkg_describe.go
pkg/actions/pkg_describe.go
+3
-1
pkg/actions/pkg_describe_test.go
pkg/actions/pkg_describe_test.go
+3
-2
pkg/actions/pkg_install.go
pkg/actions/pkg_install.go
+6
-2
pkg/actions/pkg_install_test.go
pkg/actions/pkg_install_test.go
+5
-4
pkg/actions/pkg_list.go
pkg/actions/pkg_list.go
+10
-5
pkg/actions/pkg_list_test.go
pkg/actions/pkg_list_test.go
+4
-3
pkg/actions/prototype_describe.go
pkg/actions/prototype_describe.go
+3
-1
pkg/actions/prototype_describe_test.go
pkg/actions/prototype_describe_test.go
+3
-2
pkg/actions/prototype_list.go
pkg/actions/prototype_list.go
+3
-1
pkg/actions/prototype_list_test.go
pkg/actions/prototype_list_test.go
+3
-2
pkg/actions/prototype_preview.go
pkg/actions/prototype_preview.go
+3
-1
pkg/actions/prototype_preview_test.go
pkg/actions/prototype_preview_test.go
+8
-6
pkg/actions/prototype_search.go
pkg/actions/prototype_search.go
+3
-1
pkg/actions/prototype_search_test.go
pkg/actions/prototype_search_test.go
+4
-3
pkg/actions/prototype_use.go
pkg/actions/prototype_use.go
+3
-1
pkg/actions/prototype_use_test.go
pkg/actions/prototype_use_test.go
+9
-6
pkg/actions/registry_add.go
pkg/actions/registry_add.go
+10
-6
pkg/actions/registry_add_test.go
pkg/actions/registry_add_test.go
+8
-6
pkg/actions/registry_describe.go
pkg/actions/registry_describe.go
+8
-4
No files found.
pkg/actions/actions.go
View file @
de033c2c
...
...
@@ -16,7 +16,11 @@
package
actions
import
(
"crypto/tls"
"fmt"
"net"
"net/http"
"time"
"github.com/ksonnet/ksonnet/pkg/app"
"github.com/ksonnet/ksonnet/pkg/client"
...
...
@@ -110,6 +114,8 @@ const (
OptionTlaVarFiles
=
"tla-var-files"
// OptionTlaVars is jsonnet tla vars.
OptionTlaVars
=
"tla-vars"
// OptionTLSSkipVerify specifies that tls server certifactes should not be verified.
OptionTLSSkipVerify
=
"tls-skip-verify"
// OptionUnset is unset option.
OptionUnset
=
"unset"
// OptionURI is uri option. Used for setting registry URI.
...
...
@@ -342,6 +348,38 @@ func (o *optionLoader) LoadApp() app.App {
return
a
}
// LoadHTTPClient loads an HTTP client based on common configuration for certificates, tls verification, timeouts, etc.
func
(
o
*
optionLoader
)
LoadHTTPClient
()
*
http
.
Client
{
tlsSkipVerify
:=
o
.
LoadOptionalBool
(
OptionTLSSkipVerify
)
tlsConfig
:=
&
tls
.
Config
{
InsecureSkipVerify
:
tlsSkipVerify
,
}
timeoutSeconds
:=
10
var
defaultTransport
http
.
RoundTripper
=
&
http
.
Transport
{
Proxy
:
http
.
ProxyFromEnvironment
,
DialContext
:
(
&
net
.
Dialer
{
Timeout
:
30
*
time
.
Second
,
KeepAlive
:
30
*
time
.
Second
,
DualStack
:
true
,
})
.
DialContext
,
MaxIdleConns
:
100
,
IdleConnTimeout
:
90
*
time
.
Second
,
TLSClientConfig
:
tlsConfig
,
TLSHandshakeTimeout
:
10
*
time
.
Second
,
ExpectContinueTimeout
:
1
*
time
.
Second
,
}
c
:=
&
http
.
Client
{
Timeout
:
time
.
Duration
(
timeoutSeconds
)
*
time
.
Second
,
Transport
:
defaultTransport
,
}
return
c
}
func
(
o
*
optionLoader
)
load
(
key
string
)
interface
{}
{
if
o
.
err
!=
nil
{
return
nil
...
...
pkg/actions/pkg_describe.go
View file @
de033c2c
...
...
@@ -48,6 +48,8 @@ type PkgDescribe struct {
func
NewPkgDescribe
(
m
map
[
string
]
interface
{})
(
*
PkgDescribe
,
error
)
{
ol
:=
newOptionLoader
(
m
)
httpClientOpt
:=
registry
.
HTTPClientOpt
(
ol
.
LoadHTTPClient
())
app
:=
ol
.
LoadApp
()
pd
:=
&
PkgDescribe
{
app
:
app
,
...
...
@@ -55,7 +57,7 @@ func NewPkgDescribe(m map[string]interface{}) (*PkgDescribe, error) {
templateSrc
:
pkgDescribeTemplate
,
out
:
os
.
Stdout
,
packageManager
:
registry
.
NewPackageManager
(
app
),
packageManager
:
registry
.
NewPackageManager
(
app
,
httpClientOpt
),
}
if
ol
.
err
!=
nil
{
...
...
pkg/actions/pkg_describe_test.go
View file @
de033c2c
...
...
@@ -153,8 +153,9 @@ func TestPkgDescribe(t *testing.T) {
a
.
On
(
"Libraries"
)
.
Return
(
libraries
,
nil
)
in
:=
map
[
string
]
interface
{}{
OptionApp
:
a
,
OptionPackageName
:
"apache"
,
OptionApp
:
a
,
OptionPackageName
:
"apache"
,
OptionTLSSkipVerify
:
false
,
}
pd
,
err
:=
NewPkgDescribe
(
in
)
...
...
pkg/actions/pkg_install.go
View file @
de033c2c
...
...
@@ -55,6 +55,8 @@ func NewPkgInstall(m map[string]interface{}) (*PkgInstall, error) {
if
ol
.
err
!=
nil
{
return
nil
,
ol
.
err
}
httpClient
:=
ol
.
LoadHTTPClient
()
httpClientOpt
:=
registry
.
HTTPClientOpt
(
httpClient
)
nl
:=
&
PkgInstall
{
app
:
a
,
...
...
@@ -62,9 +64,11 @@ func NewPkgInstall(m map[string]interface{}) (*PkgInstall, error) {
customName
:
ol
.
LoadString
(
OptionName
),
force
:
ol
.
LoadBool
(
OptionForce
),
envName
:
ol
.
LoadOptionalString
(
OptionEnvName
),
checker
:
registry
.
NewPackageManager
(
a
),
checker
:
registry
.
NewPackageManager
(
a
,
httpClientOpt
),
libCacherFn
:
registry
.
CacheDependency
,
libCacherFn
:
func
(
a
app
.
App
,
checker
registry
.
InstalledChecker
,
d
pkg
.
Descriptor
,
customName
string
,
force
bool
)
(
*
app
.
LibraryConfig
,
error
)
{
return
registry
.
CacheDependency
(
a
,
checker
,
d
,
customName
,
force
,
httpClient
)
},
libUpdateFn
:
a
.
UpdateLib
,
}
...
...
pkg/actions/pkg_install_test.go
View file @
de033c2c
...
...
@@ -32,10 +32,11 @@ func TestPkgInstall(t *testing.T) {
customName
:=
"customName"
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionLibName
:
libName
,
OptionName
:
customName
,
OptionForce
:
false
,
OptionApp
:
appMock
,
OptionLibName
:
libName
,
OptionName
:
customName
,
OptionForce
:
false
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewPkgInstall
(
in
)
...
...
pkg/actions/pkg_list.go
View file @
de033c2c
...
...
@@ -58,15 +58,20 @@ type PkgList struct {
func
NewPkgList
(
m
map
[
string
]
interface
{})
(
*
PkgList
,
error
)
{
ol
:=
newOptionLoader
(
m
)
app
:=
ol
.
LoadApp
()
a
:=
ol
.
LoadApp
()
httpClient
:=
ol
.
LoadHTTPClient
()
httpClientOpt
:=
registry
.
HTTPClientOpt
(
httpClient
)
rl
:=
&
PkgList
{
app
:
a
pp
,
pm
:
registry
.
NewPackageManager
(
a
pp
),
app
:
a
,
pm
:
registry
.
NewPackageManager
(
a
,
httpClientOpt
),
onlyInstalled
:
ol
.
LoadBool
(
OptionInstalled
),
outputType
:
ol
.
LoadOptionalString
(
OptionOutput
),
registryListFn
:
registry
.
List
,
out
:
os
.
Stdout
,
registryListFn
:
func
(
ksApp
app
.
App
)
([]
registry
.
Registry
,
error
)
{
return
registry
.
List
(
ksApp
,
httpClient
)
},
out
:
os
.
Stdout
,
}
if
ol
.
err
!=
nil
{
...
...
pkg/actions/pkg_list_test.go
View file @
de033c2c
...
...
@@ -152,9 +152,10 @@ func TestPkgList(t *testing.T) {
for
_
,
tc
:=
range
cases
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionInstalled
:
tc
.
onlyInstalled
,
OptionOutput
:
tc
.
outputType
,
OptionApp
:
appMock
,
OptionInstalled
:
tc
.
onlyInstalled
,
OptionOutput
:
tc
.
outputType
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewPkgList
(
in
)
...
...
pkg/actions/prototype_describe.go
View file @
de033c2c
...
...
@@ -51,12 +51,14 @@ func NewPrototypeDescribe(m map[string]interface{}) (*PrototypeDescribe, error)
ol
:=
newOptionLoader
(
m
)
app
:=
ol
.
LoadApp
()
httpClientOpt
:=
registry
.
HTTPClientOpt
(
ol
.
LoadHTTPClient
())
pd
:=
&
PrototypeDescribe
{
app
:
app
,
query
:
ol
.
LoadString
(
OptionQuery
),
out
:
os
.
Stdout
,
packageManager
:
registry
.
NewPackageManager
(
app
),
packageManager
:
registry
.
NewPackageManager
(
app
,
httpClientOpt
),
}
if
ol
.
err
!=
nil
{
...
...
pkg/actions/prototype_describe_test.go
View file @
de033c2c
...
...
@@ -33,8 +33,9 @@ func TestPrototypeDescribe(t *testing.T) {
manager
.
On
(
"Prototypes"
)
.
Return
(
prototypes
,
nil
)
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionQuery
:
"namespace"
,
OptionApp
:
appMock
,
OptionQuery
:
"namespace"
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewPrototypeDescribe
(
in
)
...
...
pkg/actions/prototype_list.go
View file @
de033c2c
...
...
@@ -51,12 +51,14 @@ func NewPrototypeList(m map[string]interface{}) (*PrototypeList, error) {
ol
:=
newOptionLoader
(
m
)
app
:=
ol
.
LoadApp
()
httpClientOpt
:=
registry
.
HTTPClientOpt
(
ol
.
LoadHTTPClient
())
pl
:=
&
PrototypeList
{
app
:
app
,
out
:
os
.
Stdout
,
outputType
:
ol
.
LoadOptionalString
(
OptionOutput
),
packageManager
:
registry
.
NewPackageManager
(
app
),
packageManager
:
registry
.
NewPackageManager
(
app
,
httpClientOpt
),
}
if
ol
.
err
!=
nil
{
...
...
pkg/actions/prototype_list_test.go
View file @
de033c2c
...
...
@@ -58,8 +58,9 @@ func TestPrototypeList(t *testing.T) {
manager
.
On
(
"Prototypes"
)
.
Return
(
prototypes
,
nil
)
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionOutput
:
tc
.
outputType
,
OptionApp
:
appMock
,
OptionOutput
:
tc
.
outputType
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewPrototypeList
(
in
)
...
...
pkg/actions/prototype_preview.go
View file @
de033c2c
...
...
@@ -62,13 +62,15 @@ func NewPrototypePreview(m map[string]interface{}) (*PrototypePreview, error) {
ol
:=
newOptionLoader
(
m
)
app
:=
ol
.
LoadApp
()
httpClientOpt
:=
registry
.
HTTPClientOpt
(
ol
.
LoadHTTPClient
())
pp
:=
&
PrototypePreview
{
app
:
app
,
query
:
ol
.
LoadString
(
OptionQuery
),
args
:
ol
.
LoadStringSlice
(
OptionArguments
),
out
:
os
.
Stdout
,
packageManager
:
registry
.
NewPackageManager
(
app
),
packageManager
:
registry
.
NewPackageManager
(
app
,
httpClientOpt
),
bindFlagsFn
:
prototype
.
BindFlags
,
extractParametersFn
:
prototype
.
ExtractParameters
,
}
...
...
pkg/actions/prototype_preview_test.go
View file @
de033c2c
...
...
@@ -41,9 +41,10 @@ func TestPrototypePreview(t *testing.T) {
}
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionQuery
:
"single-port-deployment"
,
OptionArguments
:
args
,
OptionApp
:
appMock
,
OptionQuery
:
"single-port-deployment"
,
OptionArguments
:
args
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewPrototypePreview
(
in
)
...
...
@@ -74,9 +75,10 @@ func TestPrototypePreview_bind_flags_failed(t *testing.T) {
}
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionQuery
:
"single-port-deployment"
,
OptionArguments
:
args
,
OptionApp
:
appMock
,
OptionQuery
:
"single-port-deployment"
,
OptionArguments
:
args
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewPrototypePreview
(
in
)
...
...
pkg/actions/prototype_search.go
View file @
de033c2c
...
...
@@ -54,13 +54,15 @@ func NewPrototypeSearch(m map[string]interface{}) (*PrototypeSearch, error) {
ol
:=
newOptionLoader
(
m
)
app
:=
ol
.
LoadApp
()
httpClientOpt
:=
registry
.
HTTPClientOpt
(
ol
.
LoadHTTPClient
())
ps
:=
&
PrototypeSearch
{
app
:
app
,
query
:
ol
.
LoadString
(
OptionQuery
),
outputType
:
ol
.
LoadOptionalString
(
OptionOutput
),
out
:
os
.
Stdout
,
packageManager
:
registry
.
NewPackageManager
(
app
),
packageManager
:
registry
.
NewPackageManager
(
app
,
httpClientOpt
),
protoSearchFn
:
protoSearch
,
}
...
...
pkg/actions/prototype_search_test.go
View file @
de033c2c
...
...
@@ -58,9 +58,10 @@ func TestPrototypeSearch(t *testing.T) {
manager
.
On
(
"Prototypes"
)
.
Return
(
prototypes
,
nil
)
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionQuery
:
"search"
,
OptionOutput
:
tc
.
outputType
,
OptionApp
:
appMock
,
OptionQuery
:
"search"
,
OptionOutput
:
tc
.
outputType
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewPrototypeSearch
(
in
)
...
...
pkg/actions/prototype_use.go
View file @
de033c2c
...
...
@@ -57,12 +57,14 @@ func NewPrototypeUse(m map[string]interface{}) (*PrototypeUse, error) {
ol
:=
newOptionLoader
(
m
)
app
:=
ol
.
LoadApp
()
httpClientOpt
:=
registry
.
HTTPClientOpt
(
ol
.
LoadHTTPClient
())
pl
:=
&
PrototypeUse
{
app
:
app
,
args
:
ol
.
LoadStringSlice
(
OptionArguments
),
out
:
os
.
Stdout
,
packageManager
:
registry
.
NewPackageManager
(
app
),
packageManager
:
registry
.
NewPackageManager
(
app
,
httpClientOpt
),
createComponentFn
:
component
.
Create
,
bindFlagsFn
:
prototype
.
BindFlags
,
extractParametersFn
:
prototype
.
ExtractParameters
,
...
...
pkg/actions/prototype_use_test.go
View file @
de033c2c
...
...
@@ -45,8 +45,9 @@ func TestPrototypeUse(t *testing.T) {
}
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionArguments
:
args
,
OptionApp
:
appMock
,
OptionArguments
:
args
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewPrototypeUse
(
in
)
...
...
@@ -92,8 +93,9 @@ func TestPrototypeUse_bind_flags_failed(t *testing.T) {
}
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionArguments
:
args
,
OptionApp
:
appMock
,
OptionArguments
:
args
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewPrototypeUse
(
in
)
...
...
@@ -143,8 +145,9 @@ func TestPrototypeUse_with_module_in_name(t *testing.T) {
}
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionArguments
:
args
,
OptionApp
:
appMock
,
OptionArguments
:
args
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewPrototypeUse
(
in
)
...
...
pkg/actions/registry_add.go
View file @
de033c2c
...
...
@@ -16,6 +16,7 @@
package
actions
import
(
"net/http"
"net/url"
"strings"
...
...
@@ -36,11 +37,13 @@ func RunRegistryAdd(m map[string]interface{}) error {
// RegistryAdd adds a registry.
type
RegistryAdd
struct
{
app
app
.
App
name
string
uri
string
isOverride
bool
registryAddFn
func
(
a
app
.
App
,
protocol
registry
.
Protocol
,
name
string
,
uri
string
,
isOverride
bool
)
(
*
registry
.
Spec
,
error
)
app
app
.
App
name
string
uri
string
isOverride
bool
httpClient
*
http
.
Client
registryAddFn
func
(
a
app
.
App
,
protocol
registry
.
Protocol
,
name
string
,
uri
string
,
isOverride
bool
,
httpClient
*
http
.
Client
)
(
*
registry
.
Spec
,
error
)
}
// NewRegistryAdd creates an instance of RegistryAdd.
...
...
@@ -52,6 +55,7 @@ func NewRegistryAdd(m map[string]interface{}) (*RegistryAdd, error) {
name
:
ol
.
LoadString
(
OptionName
),
uri
:
ol
.
LoadString
(
OptionURI
),
isOverride
:
ol
.
LoadBool
(
OptionOverride
),
httpClient
:
ol
.
LoadHTTPClient
(),
registryAddFn
:
registry
.
Add
,
}
...
...
@@ -70,7 +74,7 @@ func (ra *RegistryAdd) Run() error {
return
errors
.
Wrap
(
err
,
"detect registry protocol"
)
}
_
,
err
=
ra
.
registryAddFn
(
ra
.
app
,
rd
.
Protocol
,
ra
.
name
,
rd
.
URI
,
ra
.
isOverride
)
_
,
err
=
ra
.
registryAddFn
(
ra
.
app
,
rd
.
Protocol
,
ra
.
name
,
rd
.
URI
,
ra
.
isOverride
,
ra
.
httpClient
)
return
err
}
...
...
pkg/actions/registry_add_test.go
View file @
de033c2c
...
...
@@ -16,6 +16,7 @@
package
actions
import
(
"net/http"
"testing"
"github.com/ksonnet/ksonnet/pkg/app"
...
...
@@ -85,17 +86,18 @@ func TestRegistryAdd(t *testing.T) {
for
_
,
tc
:=
range
cases
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
in
:=
map
[
string
]
interface
{}{
OptionApp
:
appMock
,
OptionName
:
name
,
OptionURI
:
tc
.
uri
,
OptionVersion
:
tc
.
version
,
OptionOverride
:
tc
.
isOverride
,
OptionApp
:
appMock
,
OptionName
:
name
,
OptionURI
:
tc
.
uri
,
OptionVersion
:
tc
.
version
,
OptionOverride
:
tc
.
isOverride
,
OptionTLSSkipVerify
:
false
,
}
a
,
err
:=
NewRegistryAdd
(
in
)
require
.
NoError
(
t
,
err
)
a
.
registryAddFn
=
func
(
a
app
.
App
,
protocol
registry
.
Protocol
,
name
string
,
uri
string
,
isOverride
bool
)
(
*
registry
.
Spec
,
error
)
{
a
.
registryAddFn
=
func
(
a
app
.
App
,
protocol
registry
.
Protocol
,
name
string
,
uri
string
,
isOverride
bool
,
httpClient
*
http
.
Client
)
(
*
registry
.
Spec
,
error
)
{
assert
.
Equal
(
t
,
"new"
,
name
)
assert
.
Equal
(
t
,
tc
.
protocol
,
protocol
)
assert
.
Equal
(
t
,
tc
.
expectedURI
,
uri
)
...
...
pkg/actions/registry_describe.go
View file @
de033c2c
...
...
@@ -18,6 +18,7 @@ package actions
import
(
"fmt"
"io"
"net/http"
"os"
"sort"
...
...
@@ -48,12 +49,15 @@ type RegistryDescribe struct {
func
NewRegistryDescribe
(
m
map
[
string
]
interface
{})
(
*
RegistryDescribe
,
error
)
{
ol
:=
newOptionLoader
(
m
)
httpClient
:=
ol
.
LoadHTTPClient
()
rd
:=
&
RegistryDescribe
{
app
:
ol
.
LoadApp
(),
name
:
ol
.
LoadString
(
OptionName
),
out
:
os
.
Stdout
,
fetchRegistrySpecFn
:
fetchRegistrySpec
,
out
:
os
.
Stdout
,
fetchRegistrySpecFn
:
func
(
a
app
.
App
,
name
string
)
(
*
registry
.
Spec
,
*
app
.
RegistryConfig
,
error
)
{
return
fetchRegistrySpec
(
a
,
name
,
httpClient
)
},
}
if
ol
.
err
!=
nil
{
...
...
@@ -93,7 +97,7 @@ func (rd *RegistryDescribe) Run() error {
return
nil
}
func
fetchRegistrySpec
(
a
app
.
App
,
name
string
)
(
*
registry
.
Spec
,
*
app
.
RegistryConfig
,
error
)
{
func
fetchRegistrySpec
(
a
app
.
App
,
name
string
,
httpClient
*
http
.
Client
)
(
*
registry
.
Spec
,
*
app
.
RegistryConfig
,
error
)
{
appRegistries
,
err
:=
a
.
Registries
()
if
err
!=
nil
{
return
nil
,
nil
,
err
...
...
@@ -103,7 +107,7 @@ func fetchRegistrySpec(a app.App, name string) (*registry.Spec, *app.RegistryCon
return
nil
,
nil
,
errors
.
Errorf
(
"registry %q doesn't exist"
,
name
)
}
r
,
err
:=
registry
.
Locate
(
a
,
regRef
)
r
,
err
:=
registry
.
Locate
(
a
,
regRef
,
httpClient
)
if
err
!=
nil
{
return
nil
,
nil
,
err
}
...
...
Prev
1
2
3
4
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment