Delete deprecated API versions
pkg/service: There were a couple of references here just as a reminder to change the behavior of findPort. As of v1beta3, TargetPort was always defaulted, so we could remove findDefaultPort and related tests. pkg/apiserver: The tests were using versioned API codecs for some of their encoding tests. Necessary API types had to be written and registered with the fake versioned codecs. pkg/kubectl: Some tests were converted to current versions where it made sense.
This commit is contained in:
@@ -24,14 +24,12 @@ import (
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
utilyaml "github.com/GoogleCloudPlatform/kubernetes/pkg/util/yaml"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
@@ -144,68 +142,3 @@ func tryDecodePodList(data []byte, defaultFn defaultFunc) (parsed bool, pods api
|
||||
}
|
||||
return true, *newPods, err
|
||||
}
|
||||
|
||||
func tryDecodeSingleManifest(data []byte, defaultFn defaultFunc) (parsed bool, manifest v1beta1.ContainerManifest, pod *api.Pod, err error) {
|
||||
// TODO: should be api.Scheme.Decode
|
||||
// This is awful. DecodeInto() expects to find an APIObject, which
|
||||
// Manifest is not. We keep reading manifest for now for compat, but
|
||||
// we will eventually change it to read Pod (at which point this all
|
||||
// becomes nicer). Until then, we assert that the ContainerManifest
|
||||
// structure on disk is always v1beta1. Read that, convert it to a
|
||||
// "current" ContainerManifest (should be ~identical), then convert
|
||||
// that to a Pod (which is a well-understood conversion). This
|
||||
// avoids writing a v1beta1.ContainerManifest -> api.Pod
|
||||
// conversion which would be identical to the api.ContainerManifest ->
|
||||
// api.Pod conversion.
|
||||
pod = new(api.Pod)
|
||||
if err = yaml.Unmarshal(data, &manifest); err != nil {
|
||||
return false, manifest, pod, err
|
||||
}
|
||||
newManifest := api.ContainerManifest{}
|
||||
if err = api.Scheme.Convert(&manifest, &newManifest); err != nil {
|
||||
return false, manifest, pod, err
|
||||
}
|
||||
if errs := validation.ValidateManifest(&newManifest); len(errs) > 0 {
|
||||
err = fmt.Errorf("invalid manifest: %v", errs)
|
||||
return false, manifest, pod, err
|
||||
}
|
||||
if err = api.Scheme.Convert(&newManifest, pod); err != nil {
|
||||
return true, manifest, pod, err
|
||||
}
|
||||
if err := defaultFn(pod); err != nil {
|
||||
return true, manifest, pod, err
|
||||
}
|
||||
// Success.
|
||||
return true, manifest, pod, nil
|
||||
}
|
||||
|
||||
func tryDecodeManifestList(data []byte, defaultFn defaultFunc) (parsed bool, manifests []v1beta1.ContainerManifest, pods api.PodList, err error) {
|
||||
// TODO: should be api.Scheme.Decode
|
||||
// See the comment in tryDecodeSingle().
|
||||
if err = yaml.Unmarshal(data, &manifests); err != nil {
|
||||
return false, manifests, pods, err
|
||||
}
|
||||
newManifests := []api.ContainerManifest{}
|
||||
if err = api.Scheme.Convert(&manifests, &newManifests); err != nil {
|
||||
return false, manifests, pods, err
|
||||
}
|
||||
for i := range newManifests {
|
||||
manifest := &newManifests[i]
|
||||
if errs := validation.ValidateManifest(manifest); len(errs) > 0 {
|
||||
err = fmt.Errorf("invalid manifest: %v", errs)
|
||||
return false, manifests, pods, err
|
||||
}
|
||||
}
|
||||
list := api.ContainerManifestList{Items: newManifests}
|
||||
if err = api.Scheme.Convert(&list, &pods); err != nil {
|
||||
return true, manifests, pods, err
|
||||
}
|
||||
for i := range pods.Items {
|
||||
pod := &pods.Items[i]
|
||||
if err := defaultFn(pod); err != nil {
|
||||
return true, manifests, pods, err
|
||||
}
|
||||
}
|
||||
// Success.
|
||||
return true, manifests, pods, nil
|
||||
}
|
||||
|
@@ -148,15 +148,6 @@ func (s *sourceFile) extractFromFile(filename string) (pod *api.Pod, err error)
|
||||
return s.applyDefaults(pod, filename)
|
||||
}
|
||||
|
||||
parsed, _, pod, manifestErr := tryDecodeSingleManifest(data, defaultFn)
|
||||
if parsed {
|
||||
if manifestErr != nil {
|
||||
// It parsed but could not be used.
|
||||
return pod, manifestErr
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
parsed, pod, podErr := tryDecodeSinglePod(data, defaultFn)
|
||||
if parsed {
|
||||
if podErr != nil {
|
||||
@@ -165,7 +156,6 @@ func (s *sourceFile) extractFromFile(filename string) (pod *api.Pod, err error)
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
return pod, fmt.Errorf("%v: read '%v', but couldn't parse as neither "+
|
||||
"manifest (%v) nor pod (%v).\n",
|
||||
filename, string(data), manifestErr, podErr)
|
||||
return pod, fmt.Errorf("%v: read '%v', but couldn't parse as pod(%v).\n",
|
||||
filename, string(data), podErr)
|
||||
}
|
||||
|
@@ -17,22 +17,18 @@ limitations under the License.
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/securitycontext"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
)
|
||||
|
||||
func TestExtractFromNonExistentFile(t *testing.T) {
|
||||
@@ -339,111 +335,3 @@ func TestExtractFromEmptyDir(t *testing.T) {
|
||||
t.Errorf("Expected %#v, Got %#v", expected, update)
|
||||
}
|
||||
}
|
||||
|
||||
func exampleManifestAndPod(id string) (v1beta1.ContainerManifest, *api.Pod) {
|
||||
hostname := "an-example-host"
|
||||
|
||||
manifest := v1beta1.ContainerManifest{
|
||||
Version: "v1beta1",
|
||||
ID: id,
|
||||
UUID: types.UID(id),
|
||||
Containers: []v1beta1.Container{
|
||||
{
|
||||
Name: "c" + id,
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/somepath",
|
||||
},
|
||||
},
|
||||
Volumes: []v1beta1.Volume{
|
||||
{
|
||||
Name: "host-dir",
|
||||
Source: v1beta1.VolumeSource{
|
||||
HostDir: &v1beta1.HostPathVolumeSource{"/dir/path"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedPod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: id + "-" + hostname,
|
||||
UID: types.UID(id),
|
||||
Namespace: kubelet.NamespaceDefault,
|
||||
SelfLink: getSelfLink(id+"-"+hostname, kubelet.NamespaceDefault),
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
NodeName: hostname,
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "c" + id,
|
||||
Image: "foo",
|
||||
},
|
||||
},
|
||||
Volumes: []api.Volume{
|
||||
{
|
||||
Name: "host-dir",
|
||||
VolumeSource: api.VolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{"/dir/path"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return manifest, expectedPod
|
||||
}
|
||||
|
||||
func TestExtractFromDir(t *testing.T) {
|
||||
if !api.PreV1Beta3(testapi.Version()) {
|
||||
return
|
||||
}
|
||||
manifest, expectedPod := exampleManifestAndPod("1")
|
||||
manifest2, expectedPod2 := exampleManifestAndPod("2")
|
||||
|
||||
manifests := []v1beta1.ContainerManifest{manifest, manifest2}
|
||||
pods := []*api.Pod{expectedPod, expectedPod2}
|
||||
files := make([]*os.File, len(manifests))
|
||||
|
||||
dirName, err := ioutil.TempDir("", "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
for i, manifest := range manifests {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
file, err := ioutil.TempFile(dirName, manifest.ID)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
name := file.Name()
|
||||
if err := file.Close(); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
ioutil.WriteFile(name, data, 0755)
|
||||
files[i] = file
|
||||
}
|
||||
|
||||
ch := make(chan interface{}, 1)
|
||||
c := sourceFile{dirName, "an-example-host", ch}
|
||||
err = c.extractFromPath()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
update := (<-ch).(kubelet.PodUpdate)
|
||||
expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource, pods...)
|
||||
sort.Sort(sortedPods(update.Pods))
|
||||
sort.Sort(sortedPods(expected.Pods))
|
||||
if !api.Semantic.DeepDerivative(expected, update) {
|
||||
t.Fatalf("Expected %#v, Got %#v", expected, update)
|
||||
}
|
||||
for _, pod := range update.Pods {
|
||||
if errs := validation.ValidatePod(pod); len(errs) != 0 {
|
||||
t.Errorf("Expected no validation errors on %#v, Got %q", pod, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -77,50 +77,12 @@ func (s *sourceURL) extractFromURL() error {
|
||||
s.updates <- kubelet.PodUpdate{[]*api.Pod{}, kubelet.SET, kubelet.HTTPSource}
|
||||
return fmt.Errorf("zero-length data received from %v", s.url)
|
||||
}
|
||||
// Short circuit if the manifest has not changed since the last time it was read.
|
||||
// Short circuit if the data has not changed since the last time it was read.
|
||||
if bytes.Compare(data, s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
s.data = data
|
||||
|
||||
// First try as if it's a single manifest
|
||||
parsed, manifest, pod, singleErr := tryDecodeSingleManifest(data, s.applyDefaults)
|
||||
if parsed {
|
||||
if singleErr != nil {
|
||||
// It parsed but could not be used.
|
||||
return singleErr
|
||||
}
|
||||
// It parsed!
|
||||
s.updates <- kubelet.PodUpdate{[]*api.Pod{pod}, kubelet.SET, kubelet.HTTPSource}
|
||||
return nil
|
||||
}
|
||||
|
||||
// That didn't work, so try an array of manifests.
|
||||
parsed, manifests, podList, multiErr := tryDecodeManifestList(data, s.applyDefaults)
|
||||
if parsed {
|
||||
if multiErr != nil {
|
||||
// It parsed but could not be used.
|
||||
return multiErr
|
||||
}
|
||||
// A single manifest that did not pass semantic validation will yield an empty
|
||||
// array of manifests (and no error) when unmarshaled as such. In that case,
|
||||
// if the single manifest at least had a Version, we return the single-manifest
|
||||
// error (if any).
|
||||
if len(manifests) == 0 && len(manifest.Version) != 0 {
|
||||
return singleErr
|
||||
}
|
||||
// It parsed!
|
||||
pods := make([]*api.Pod, 0)
|
||||
for i := range podList.Items {
|
||||
pods = append(pods, &podList.Items[i])
|
||||
}
|
||||
s.updates <- kubelet.PodUpdate{pods, kubelet.SET, kubelet.HTTPSource}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parsing it as ContainerManifest(s) failed.
|
||||
// Try to parse it as Pod(s).
|
||||
|
||||
// First try as it is a single pod.
|
||||
parsed, pod, singlePodErr := tryDecodeSinglePod(data, s.applyDefaults)
|
||||
if parsed {
|
||||
@@ -147,9 +109,7 @@ func (s *sourceURL) extractFromURL() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%v: received '%v', but couldn't parse as neither "+
|
||||
"single (%v: %+v) or multiple manifests (%v: %+v) nor "+
|
||||
return fmt.Errorf("%v: received '%v', but couldn't parse as "+
|
||||
"single (%v) or multiple pods (%v).\n",
|
||||
s.url, string(data), singleErr, manifest, multiErr, manifests,
|
||||
singlePodErr, multiPodErr)
|
||||
s.url, string(data), singlePodErr, multiPodErr)
|
||||
}
|
||||
|
@@ -24,7 +24,6 @@ import (
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
@@ -118,195 +117,6 @@ func TestExtractInvalidManifest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractManifestFromHTTP(t *testing.T) {
|
||||
hostname := "random-hostname"
|
||||
// ContainerManifests are not supported v1beta3 onwards.
|
||||
if api.PreV1Beta3(testapi.Version()) {
|
||||
return
|
||||
}
|
||||
|
||||
var testCases = []struct {
|
||||
desc string
|
||||
manifests interface{}
|
||||
expected kubelet.PodUpdate
|
||||
}{
|
||||
{
|
||||
desc: "Single manifest",
|
||||
manifests: v1beta1.ContainerManifest{Version: "v1beta1", ID: "foo", UUID: "111",
|
||||
Containers: []v1beta1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1beta1.PullAlways}}},
|
||||
expected: CreatePodUpdate(kubelet.SET,
|
||||
kubelet.HTTPSource,
|
||||
&api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
UID: "111",
|
||||
Name: "foo" + "-" + hostname,
|
||||
Namespace: "foobar",
|
||||
|
||||
SelfLink: getSelfLink("foo-"+hostname, kubelet.NamespaceDefault),
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
NodeName: hostname,
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{
|
||||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
desc: "Single manifest without ID",
|
||||
manifests: v1beta1.ContainerManifest{Version: "v1beta1", UUID: "111",
|
||||
Containers: []v1beta1.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}},
|
||||
expected: CreatePodUpdate(kubelet.SET,
|
||||
kubelet.HTTPSource,
|
||||
&api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
UID: "111",
|
||||
Name: "111" + "-" + hostname,
|
||||
Namespace: "foobar",
|
||||
|
||||
SelfLink: getSelfLink("111-"+hostname, kubelet.NamespaceDefault),
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
NodeName: hostname,
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{
|
||||
Name: "ctr",
|
||||
Image: "image",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
desc: "Single manifest with v1beta2",
|
||||
manifests: v1beta1.ContainerManifest{Version: "v1beta2", ID: "foo", UUID: "111",
|
||||
Containers: []v1beta1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1beta1.PullAlways}}},
|
||||
expected: CreatePodUpdate(kubelet.SET,
|
||||
kubelet.HTTPSource,
|
||||
&api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
UID: "111",
|
||||
Name: "foo" + "-" + hostname,
|
||||
Namespace: "foobar",
|
||||
|
||||
SelfLink: getSelfLink("foo-"+hostname, kubelet.NamespaceDefault),
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
NodeName: hostname,
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{
|
||||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
desc: "Multiple manifests",
|
||||
manifests: []v1beta1.ContainerManifest{
|
||||
{Version: "v1beta1", ID: "foo", UUID: "111",
|
||||
Containers: []v1beta1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1beta1.PullAlways}}},
|
||||
{Version: "v1beta1", ID: "bar", UUID: "222",
|
||||
Containers: []v1beta1.Container{{Name: "1", Image: "foo", ImagePullPolicy: ""}}},
|
||||
},
|
||||
expected: CreatePodUpdate(kubelet.SET,
|
||||
kubelet.HTTPSource,
|
||||
&api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
UID: "111",
|
||||
Name: "foo" + "-" + hostname,
|
||||
Namespace: "foobar",
|
||||
|
||||
SelfLink: getSelfLink("foo-"+hostname, kubelet.NamespaceDefault),
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
NodeName: hostname,
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{
|
||||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
},
|
||||
&api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
UID: "222",
|
||||
Name: "bar" + "-" + hostname,
|
||||
Namespace: "foobar",
|
||||
|
||||
SelfLink: getSelfLink("bar-"+hostname, kubelet.NamespaceDefault),
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
NodeName: hostname,
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{
|
||||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
desc: "Empty Array",
|
||||
manifests: []v1beta1.ContainerManifest{},
|
||||
expected: CreatePodUpdate(kubelet.SET, kubelet.HTTPSource),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
data, err := json.Marshal(testCase.manifests)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Some weird json problem: %v", testCase.desc, err)
|
||||
}
|
||||
fakeHandler := util.FakeHandler{
|
||||
StatusCode: 200,
|
||||
ResponseBody: string(data),
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
defer testServer.Close()
|
||||
ch := make(chan interface{}, 1)
|
||||
c := sourceURL{testServer.URL, hostname, ch, nil}
|
||||
if err := c.extractFromURL(); err != nil {
|
||||
t.Errorf("%s: Unexpected error: %v", testCase.desc, err)
|
||||
continue
|
||||
}
|
||||
update := (<-ch).(kubelet.PodUpdate)
|
||||
|
||||
for i := range update.Pods {
|
||||
// There's no way to provide namespace in ContainerManifest, so
|
||||
// it will be defaulted.
|
||||
if update.Pods[i].Namespace != kubelet.NamespaceDefault {
|
||||
t.Errorf("Unexpected namespace: %s", update.Pods[0].Namespace)
|
||||
}
|
||||
update.Pods[i].ObjectMeta.Namespace = "foobar"
|
||||
}
|
||||
if !api.Semantic.DeepEqual(testCase.expected, update) {
|
||||
t.Errorf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update)
|
||||
}
|
||||
for _, pod := range update.Pods {
|
||||
if errs := validation.ValidatePod(pod); len(errs) != 0 {
|
||||
t.Errorf("%s: Expected no validation errors on %#v, Got %v", testCase.desc, pod, errors.NewAggregate(errs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractPodsFromHTTP(t *testing.T) {
|
||||
hostname := "different-value"
|
||||
|
||||
|
Reference in New Issue
Block a user