Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions pkg/ansible/paramconv/paramconv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright 2018 The Operator-SDK Authors
//
// 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.

// Based on https://github.com/iancoleman/strcase

package paramconv

import (
"regexp"
"strings"
)

var (
numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`)
numberReplacement = []byte(`$1 $2 $3`)
wordMapping = map[string]string{
"http": "HTTP",
"url": "URL",
"ip": "IP",
}
)

func addWordBoundariesToNumbers(s string) string {
b := []byte(s)
b = numberSequence.ReplaceAll(b, numberReplacement)
return string(b)
}

func translateWord(word string, initCase bool) string {
if val, ok := wordMapping[word]; ok {
return val
}
if initCase {
return strings.Title(word)
}
return word
}

// Converts a string to CamelCase
func ToCamel(s string) string {
s = addWordBoundariesToNumbers(s)
s = strings.Trim(s, " ")
n := ""
bits := []string{}
for _, v := range s {
if v == '_' || v == ' ' || v == '-' {
bits = append(bits, n)
n = ""
} else {
n += string(v)
}
}
bits = append(bits, n)

ret := ""
for i, substr := range bits {
ret += translateWord(substr, i != 0)
}
return ret
}

// Converts a string to snake_case
func ToSnake(s string) string {
s = addWordBoundariesToNumbers(s)
s = strings.Trim(s, " ")
var prefix string
char1 := []rune(s)[0]
if char1 >= 'A' && char1 <= 'Z' {
prefix = "_"
} else {
prefix = ""
}
bits := []string{}
n := ""
real_i := -1

for i, v := range s {
real_i += 1
// treat acronyms as words, eg for JSONData -> JSON is a whole word
nextCaseIsChanged := false
if i+1 < len(s) {
next := s[i+1]
if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') {
nextCaseIsChanged = true
}
}

if real_i > 0 && n[len(n)-1] != '_' && nextCaseIsChanged {
// add underscore if next letter case type is changed
if v >= 'A' && v <= 'Z' {
bits = append(bits, strings.ToLower(n))
n = string(v)
real_i = 0
} else if v >= 'a' && v <= 'z' {
bits = append(bits, strings.ToLower(n+string(v)))
n = ""
real_i = -1
}
} else if v == ' ' || v == '_' || v == '-' {
// replace spaces/underscores with delimiters
bits = append(bits, strings.ToLower(n))
n = ""
real_i = -1
} else {
n = n + string(v)
}
}
bits = append(bits, strings.ToLower(n))
joined := strings.Join(bits, "_")
if _, ok := wordMapping[bits[0]]; !ok {
return prefix + joined
}
return joined
}

func convertParameter(fn func(string) string, v interface{}) interface{} {
switch v := v.(type) {
case map[string]interface{}:
ret := map[string]interface{}{}
for key, val := range v {
ret[fn(key)] = convertParameter(fn, val)
}
return ret
case []interface{}:
return convertArray(fn, v)
default:
return v
}
}

func convertArray(fn func(string) string, in []interface{}) []interface{} {
res := make([]interface{}, len(in))
for i, v := range in {
res[i] = convertParameter(fn, v)
}
return res
}

func convertMapKeys(fn func(string) string, in map[string]interface{}) map[string]interface{} {
converted := map[string]interface{}{}
for key, val := range in {
converted[fn(key)] = convertParameter(fn, val)
}
return converted
}

func MapToSnake(in map[string]interface{}) map[string]interface{} {
return convertMapKeys(ToSnake, in)
}

func MapToCamel(in map[string]interface{}) map[string]interface{} {
return convertMapKeys(ToCamel, in)
}
101 changes: 101 additions & 0 deletions pkg/ansible/proxy/kubeconfig/kubeconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2018 The Operator-SDK Authors
//
// 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.

package kubeconfig

import (
"bytes"
"encoding/base64"
"encoding/json"
"html/template"
"io/ioutil"
"net/url"
"os"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// kubectl, as of 1.10.5, only does basic auth if the username is present in
// the URL. The python client used by ansible, as of 6.0.0, only does basic
// auth if the username and password are provided under the "user" key within
// "users".
const kubeConfigTemplate = `---
apiVersion: v1
kind: Config
clusters:
- cluster:
insecure-skip-tls-verify: true
server: {{.ProxyURL}}
name: proxy-server
contexts:
- context:
cluster: proxy-server
user: admin/proxy-server
name: {{.Namespace}}/proxy-server
current-context: {{.Namespace}}/proxy-server
preferences: {}
users:
- name: admin/proxy-server
user:
username: {{.Username}}
password: unused
`

// values holds the data used to render the template
type values struct {
Username string
ProxyURL string
Namespace string
}

// Create renders a kubeconfig template and writes it to disk
func Create(ownerRef metav1.OwnerReference, proxyURL string, namespace string) (*os.File, error) {
parsedURL, err := url.Parse(proxyURL)
if err != nil {
return nil, err
}
ownerRefJSON, err := json.Marshal(ownerRef)
if err != nil {
return nil, err
}
username := base64.URLEncoding.EncodeToString([]byte(ownerRefJSON))
parsedURL.User = url.User(username)
v := values{
Username: username,
ProxyURL: parsedURL.String(),
Namespace: namespace,
}

var parsed bytes.Buffer

t := template.Must(template.New("kubeconfig").Parse(kubeConfigTemplate))
t.Execute(&parsed, v)

file, err := ioutil.TempFile("", "kubeconfig")
if err != nil {
return nil, err
}
// multiple calls to close file will not hurt anything,
// but we don't want to lose the error because we are
// writing to the file, so we will call close twice.
defer file.Close()

if _, err := file.WriteString(parsed.String()); err != nil {
return nil, err
}
if err := file.Close(); err != nil {
return nil, err
}
return file, nil
}
Loading