missing permissions for test user - go

I use a watcherList, which is supported by the official golang kubernetes lib, to get notifications about created, updated and removed services inside a kubernetes namespace. Here the snippet.
func (kc *KubernetesCollector) streamEvents(ctx context.Context) {
kc.debugChannel <- fmt.Sprintf("Start streaming events from kubernetes API")
watchList := cache.NewListWatchFromClient(kc.k8sClient.RESTClient(), "services", kc.k8sNamespace, fields.Everything())
notificationCallbackToAddService := func(svc interface{}) {
service := svc.(*v1.Service)
kc.serviceNotificationChannel <- &serviceNotification{service, "add"}
}
notificationCallbackToDeleteService := func(svc interface{}) {
service := svc.(*v1.Service)
kc.serviceNotificationChannel <- &serviceNotification{service, "remove"}
}
callbacks := cache.ResourceEventHandlerFuncs{
AddFunc: notificationCallbackToAddService,
DeleteFunc: notificationCallbackToDeleteService,
}
_, controller := cache.NewInformer(watchList, &v1.Service{}, time.Second*0, callbacks)
go controller.Run(ctx.Done())
}
In my test I declare the kc.k8sClient over the public api address, which is defined in k8sAPI variable. Additionally I set the bearer token to authenticate against the cluster and skip to verify the insecure ssl certificate.
func TestK8sWatchList(t *testing.T) {
require := require.New(t)
...
k8sConfig, err := clientcmd.BuildConfigFromFlags(k8sAPI, "")
require.NoError(err)
k8sConfig.BearerToken = "<bearerToken>"
k8sConfig.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
k8sClient, err := kubernetes.NewForConfig(k8sConfig)
k8sCollector := NewK8sCollector(k8sClient, k8sNamespace)
...
}
When I execute the test, I receive the following error messages:
go test -v -timeout 500s <replaced>/t1k/pkg/collector -run TestK8sWatchList
=== RUN TestK8sWatchList
11.02.2020 16:55:55 DEBUG: Start streaming events from kubernetes API
E0211 16:55:51.706530 121803 reflector.go:153] pkg/mod/k8s.io/client-go#v0.0.0-20200106225816-7985654fe8ee/tools/cache/reflector.go:105: Failed to list *v1.Service: forbidden: User "system:serviceaccount:t1k:t1k-test-serviceaccount" cannot get path "/namespaces/t1k/services"
E0211 16:55:52.707520 121803 reflector.go:153] pkg/mod/k8s.io/client-go#v0.0.0-20200106225816-7985654fe8ee/tools/cache/reflector.go:105: Failed to list *v1.Service: forbidden: User "system:serviceaccount:t1k:t1k-test-serviceaccount" cannot get path "/namespaces/t1k/services"
E0211 16:55:53.705539 121803 reflector.go:153] pkg/mod/k8s.io/client-go#v0.0.0-20200106225816-7985654fe8ee/tools/cache/reflector.go:105: Failed to list *v1.Service: forbidden: User "system:serviceaccount:t1k:t1k-test-serviceaccount" cannot get path "/namespaces/t1k/services"
I don't understand why I get the error message, because the service account "t1k-test-serviceaccount" has in my opinion all required permissions. Now the defined service account, role and rolebinding for the test user.
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: t1k
name: t1k-test-serviceaccount
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: t1k
name: t1k-test-role
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["*"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: t1k
name: t1k-test-rolebinding
subjects:
- name: t1k-test-serviceaccount
kind: ServiceAccount
apiGroup: ""
roleRef:
name: t1k-test-role
kind: Role
apiGroup: rbac.authorization.k8s.io
Additional informations:
kubeadm version 1.15.9
kubectl version 1.17.2
golib versions
k8s.io/api v0.17.2
k8s.io/apimachinery v0.17.2
k8s.io/client-go v0.0.0-20200106225816-7985654fe8ee
k8s.io/utils v0.0.0-20200117235808-5f6fbceb4c31 // indirect

You can check permission of the service account using below command:
kubectl auth can-i list services --namespace t1k --as=system:serviceaccount:t1k:t1k-test-serviceaccount
You don't need to set the token manually...you can use InClusterConfig as in this example.Client-go uses the Service Account token mounted inside the Pod at the /var/run/secrets/kubernetes.io/serviceaccount path when the rest.InClusterConfig() is used.

I found the solution. The k8sClientSet attribut of the KubernetesCollector struct was a pointer. The reflection function of the package pkg/mod/k8s.io/client-go#v0.0.0-20200106225816-7985654fe8ee can not handle pointer objects.
type KubernetesCollector struct {
...
k8sClient *kubernetes.ClientSet
namespace string
...
}
I replaced the k8sClient with the CoreV1Interface from k8s.io/client-go/kubernetes/typed/core/v1. Therefore I changed the call for the ListWatch.
type KubernetesCollector struct {
....
iface corev1.CoreV1Interface
namespace string
....
}
func (kc *KubernetesCollector) start(ctx context.Context) {
watchList := cache.NewListWatchFromClient(kc.iface.RESTClient(), "services", kc.namespace, fields.Everything())
....
}

Related

Getting "rpc error: code = Unavailable desc = error reading from server: EOF" when trying to create a new etcdv3 client

I'm trying to access my ETCD database from a K8s controller, but getting rpc error/EOF when trying to open ETCD client.
My setup:
ETCD service is deployed in my K8s cluster and included in my Istio service mesh (its DNS record: my-etcd-cluster.my-etcd-namespace.svc.cluster.local)
I have a custom K8s controller developed with use of Kubebuilder framework and deployed in the same cluster, different namespace, but configured to be a part of the same Istio service mesh
I'm trying to connect to ETCD database from the controller, using Go client SDK library for ETCD
Here's my affected Go code:
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{"http://my-etcd-cluster.my-etcd-namespace.svc.cluster.local:2379"},
DialTimeout: 5 * time.Second,
Username: username,
Password: password,
})
if err != nil {
return nil, fmt.Errorf("opening ETCD client failed: %v", err)
}
And here's an error I'm getting when clientv3.New(...) gets executed:
{"level":"warn","ts":"2022-03-16T23:37:42.174Z","logger":"etcd-client","caller":"v3#v3.5.0/retry_interceptor.go:62","msg":"retrying of unary invoker failed",
"target":"etcd-endpoints://0xc00057f500/#initially=[http://my-etcd-cluster.my-etcd-namespace.svc.cluster.local:2379]","attempt":0,
"error":"rpc error: code = Unavailable desc = error reading from server: EOF"}
...
1.647473862175209e+09 INFO controller.etcdclient Finish reconcile loop for some-service/test-svc-client {"reconciler group": "my-controller.something.io", "reconciler kind": "ETCDClient", "name": "test-svc-client", "namespace": "some-service", "reconcile-etcd-client": "some-service/test-svc-client"}
1.6474738621752858e+09 ERROR controller.etcdclient Reconciler error {"reconciler group": "my-controller.something.io", "reconciler kind": "ETCDClient", "name": "test-svc-client", "namespace": "some-service", "error": "opening ETCD client failed: rpc error: code = Unavailable desc = error reading from server: EOF"}
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).processNextWorkItem
/go/pkg/mod/sigs.k8s.io/controller-runtime#v0.11.0/pkg/internal/controller/controller.go:266
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Start.func2.2
/go/pkg/mod/sigs.k8s.io/controller-runtime#v0.11.0/pkg/internal/controller/controller.go:227
The same error happens when I'm passing some dummy, invalid credentials.
However, when I tried to access the database in a HTTP API manner:
postBody, _ := json.Marshal(map[string]string{
"name": username,
"password": password,
})
responseBody := bytes.NewBuffer(postBody)
resp, err := http.Post("http://my-etcd-cluster.my-etcd-namespace.svc.cluster.local:2379/v3/auth/authenticate", "application/json", responseBody)
if err != nil {
return ctrl.Result{}, fmt.Errorf("an error occured %w", err)
}
l.Info(fmt.Sprintf("code: %d", resp.StatusCode))
defer resp.Body.Close()
...I got 200 OK and a proper token (which is expected), so I believe my Istio configuration is ok and my controller should be able to see the ETCD db service. I have no clue why this doesn't work when following the client SDK approach.
When I'm using port-forwarding of the ETCD service and accessing it locally, clientv3.New() and other client SDK methods work like a charm. What am I missing? I'd really appreciate any suggestions.
EDIT:
I've also added a simple pod to try accessing my etcd db via etcdctl:
apiVersion: v1
kind: Pod
metadata:
name: test-pod
namespace: my-controller-namespace
spec:
containers:
- name: etcdctl
image: bitnami/etcd
command:
- sleep
- infinity
When logged into the container via kubectl exec, I was able to access my db:
$ etcdctl --endpoints=my-etcd-cluster.my-etcd-namespace.svc.cluster.local:2379 --user="user" --password="password" put foo bob
OK
I guess the problem is somewhere in the SDK?
Turned out to be version mismatch - my ETCD db is v3.5.2 and the clientv3 library that I used was v3.5.0.
As seen in ETCD changelog (https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.5.md):

How to create custom objects in Kubernetes? [duplicate]

This question already has an answer here:
Create/Get a custom kubernetes resource
(1 answer)
Closed 1 year ago.
I am using Velero to create and backup and restore, Velero has controllers which get triggered when I can create the custom objects.
import veleroApi "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
restoreObj := veleroApi.Restore{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
DeletionGracePeriodSeconds: &gracePeriodSeconds,
},
Spec: veleroApi.RestoreSpec{
BackupName: "backup-name-20211101",
RestorePVs: &restorePV,
},
Status: veleroApi.RestoreStatus{},
}
But how can I submit this custom object to the Kube API server?
I used API client to apply the changes:
apiClient.CoreV1().RESTClient().Patch(types.ApplyPatchType).Body(restoreObj).Do(context)
But I am getting:
unknown type used for body: {TypeMeta:{Kind:Restore APIVersion:velero.io/v1} ObjectMeta:{Name: GenerateName: Namespace:velero SelfLink: UID: ResourceVersion: Generation:0 CreationTimestamp:0001-01-01 00:00:00 +0000 UTC DeletionTimestamp:<nil> DeletionGracePeriodSeconds:0xc000256018 Labels:map[] Annotations:map[] OwnerReferences:[] Finalizers:[] ClusterName: ManagedFields:[]} Spec:{BackupName:backup-name-20211101 ScheduleName: IncludedNamespaces:[] ExcludedNamespaces:[] IncludedResources:[] ExcludedResources:[] NamespaceMapping:map[] LabelSelector:nil RestorePVs:0xc0007a9088 PreserveNodePorts:<nil> IncludeClusterResources:<nil> Hooks:{Resources:[]}} Status:{Phase: ValidationErrors:[] Warnings:0 Errors:0 FailureReason: StartTimestamp:<nil> CompletionTimestamp:<nil> Progress:<nil>}}
If you would like to create a client for custom object follow the following steps:
Describe the custom resource for which you would like to create a rest client:
kubectl describe CustomResourceDefinition <custom resource definition name>
Note down the API and version and the Kind, as an example it would look like:
API Version: apiextensions.k8s.io/v1
Kind: CustomResourceDefinition
Here, apiextensions.k8s.io is API and v1 is the version.
Check if the API version that you got from step 1 is in the list of APIs:
kubectl get --raw "/"
Create the client:
func getClusterConfig() *rest.Config {
config, err := rest.InClusterConfig()
if err != nil {
glog.Fatal(err.Error())
}
return config
}
func getRestClient() *rest.RESTClient {
cfg := getClusterConfig()
gv := schema.GroupVersion{Group: "<API>", Version: "<version>"}
cfg.GroupVersion = &gv
cfg.APIPath = "/apis" // you can verify the path from step 2
var Scheme = runtime.NewScheme()
var Codecs = serializer.NewCodecFactory(Scheme)
cfg.NegotiatedSerializer = Codecs.WithoutConversion()
restClient, err := rest.RESTClientFor(cfg)
if err != nil {
panic(err.Error())
}
return restClient
}
Alternatively, check the answer from kozmo here
For Velero you can reuse the client they have.
As an example take a look at this code:
restore, err := o.client.VeleroV1().Restores(restore.Namespace).Create(context.TODO(), restore, metav1.CreateOptions{})

gomplate: executing "<arg>" at <.Values.tpl.organization>: map has no entry for key "Values"

Here is my yaml file
apiVersion: myapi.com/v1
kind: Template
metadata:
name: {{ .Values.tpl.organization }}-template-mkytemplatetemplate
spec:
# Add fields here
organization: {{ .Values.tpl.organization }}
purpose: {{ .Values.tpl.purpose }}
version: {{ .Chart.appVersion }}
location: {{.Values.location}}/template.tgz
name: mkytemplatetemplate
namePattern: ^[a-z0-9\-]{3,25}$
description: "# Please do not use the template\ntest modify run.sh"
author: string
I use gomplate to replace all {{xxx}} to corresponding values in Chart.yaml or Values.yaml. Here is my code
func main() {
log.Println("hello")
//BasicTemplate()
Gomplate()
}
func Gomplate() {
workspace := "/Users/i517131/code/mkytemplatetemplate/.ci/chart"
inBs, _ := ioutil.ReadFile(path.Join(workspace, "templates/template.yaml"))
in := string(inBs)
cfg := &gomplate.Config{
Input: in,
OutputFiles: []string{path.Join(workspace, "result.yaml")},
DataSources: []string{
fmt.Sprintf("%s=%s", "Chart", path.Join(workspace, "Chart.yaml")),
fmt.Sprintf("%s=%s", "Values", path.Join(workspace, "values.yaml")),
},
}
err := gomplate.RunTemplates(cfg)
if err != nil {
panic(err)
}
}
but I receive an error like this panic: template: <arg>:4:18: executing "<arg>" at <.Values.tpl.organization>: map has no entry for key "Values". at err := gomplate.RunTemplates(cfg)
At first, when I run cmd gomplate -f .ci/chart/templates/template.yaml -d Chart=.ci/chart/Chart.yaml -d Values=.ci/chart/values.yaml -o result.yaml, I receive the save error.
I searched the internet and in github, the author suggest us use -c instead of -d
But the gomplate in go can only use gomplate.Config to run templates and do not support -c. What can I do?
result generated by -c cmd
apiVersion: myapi.com/v1
kind: Template
metadata:
name: mky-template-mkytemplatetemplate
spec:
# Add fields here
organization: mky
purpose: prod
version: 1.0.0
location: _/template.tgz
name: mkytemplatetemplate
namePattern: ^[a-z0-9\-]{3,25}$
description: "# Please do not use the template\ntest modify run.sh"
author: string
use Contexts not DataSource
cfg := &gomplate.Config{
Input: in,
OutputFiles: []string{path.Join(workspace, "result.yaml")},
Contexts: []string{
fmt.Sprintf("%s=%s", "Chart", path.Join(workspace, "Chart.yaml")),
fmt.Sprintf("%s=%s", "Values", path.Join(workspace, "values.yaml")),
},
}

failed to make Query API call, ResourceNotFoundException: Cannot do operations on a non-existent table

Summary
I'm making aws lambda function by AWS SAM.
This function needs database, so I choose DynamoDB.
Now I'm setting local environment for AWS SAM and DynamoDB.
It seems that I success to set local DynamoDB, but it fails to connect when running local aws sam function.
failed to make Query API call, ResourceNotFoundException: Cannot do operations on a non-existent table
I want to know how to solve this issue.
tried
I created local table and checked test data is inserted.
❯ aws dynamodb create-table --cli-input-json file://test/positive-line-bot_table.json --endpoint-url http://localhost:8000
TABLEDESCRIPTION 1578904757.61 0 arn:aws:dynamodb:ddblocal:000000000000:table/PositiveLineBotTable PositiveLineBotTable 0 ACTIVE
ATTRIBUTEDEFINITIONS Id N
BILLINGMODESUMMARY PROVISIONED 0.0
KEYSCHEMA Id HASH
PROVISIONEDTHROUGHPUT 0.0 0.0 0 5 5
❯ aws dynamodb batch-write-item --request-items file://test/positive-line-bot_table_data.json --endpoint-url http://localhost:8000
❯ aws dynamodb list-tables --endpoint-url http://localhost:8000
TABLENAMES PositiveLineBotTable
❯ aws dynamodb get-item --table-name PositiveLineBotTable --key '{"Id":{"N":"1"}}' --endpoint-url http://localhost:8000
ID 1
NAME test
But when I run aws sam in local, it seems that it does no connect to this local DynamoDB although this table does exit in local.
❯ sam local start-api --env-vars test/env.json
Fetching lambci/lambda:go1.x Docker container image......
Mounting /Users/jpskgc/go/src/line-positive-bot/positive-line-bot as /var/task:ro,delegated inside runtime container
START RequestId: c9f19371-4fea-1e25-09ec-5f628f7fcb7a Version: $LATEST
failed to make Query API call, ResourceNotFoundException: Cannot do operations on a non-existent table
Function 'PositiveLineBotFunction' timed out after 5 seconds
Function returned an invalid response (must include one of: body, headers, multiValueHeaders or statusCode in the response object). Response received:
2020-01-13 18:46:10 127.0.0.1 - - [13/Jan/2020 18:46:10] "GET /positive HTTP/1.1" 502 -
❯ curl http://127.0.0.1:3000/positive
{"message":"Internal server error"}
I want to know how to actually connect to local DynamoDB table.
some code
Here is the function code in Go.
package main
//import
func exitWithError(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
type Item struct {
Key int
Desc string
Data map[string]interface{}
}
type Event struct {
Type string `json:"type"`
ReplyToken string `json:"replyToken"`
Source Source `json:"source"`
Timestamp int64 `json:"timestamp"`
Message Message `json:"message"`
}
type Message struct {
Type string `json:"type"`
ID string `json:"id"`
Text string `json:"text"`
}
type Source struct {
UserID string `json:"userId"`
Type string `json:"type"`
}
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
endpoint := os.Getenv("DYNAMODB_ENDPOINT")
tableName := os.Getenv("DYNAMODB_TABLE_NAME")
sess := session.Must(session.NewSession())
config := aws.NewConfig().WithRegion("ap-northeast-1")
if len(endpoint) > 0 {
config = config.WithEndpoint(endpoint)
}
svc := dynamodb.New(sess, config)
params := &dynamodb.ScanInput{
TableName: aws.String(tableName),
}
result, err := svc.Scan(params)
if err != nil {
exitWithError(fmt.Errorf("failed to make Query API call, %v", err))
}
items := []Item{}
err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &items)
if err != nil {
exitWithError(fmt.Errorf("failed to unmarshal Query result items, %v", err))
}
var words []string
for i, item := range items {
for k, v := range item.Data {
words = append(words, v.(string))
}
}
rand.Seed(time.Now().UnixNano())
i := rand.Intn(len(words))
word := words[i]
return events.APIGatewayProxyResponse{
Body: word,
StatusCode: 200,
}, nil
}
func main() {
lambda.Start(handler)
}
Here is env.json
I try changed docker.for.mac.host.internal to my local ip address. But it does not solve.
{
"PositiveLineBotFunction": {
"DYNAMODB_ENDPOINT": "http://docker.for.mac.host.internal:8000",
"DYNAMODB_TABLE_NAME": "PositiveLineBotTable"
}
}
Here is template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
positive-line-bot
Globals:
Function:
Timeout: 5
Resources:
PositiveLineBotFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: positive-line-bot/
Handler: positive-line-bot
Runtime: go1.x
Policies:
- DynamoDBReadPolicy:
TableName: !Ref PositiveLineBotTable
Tracing: Active
Events:
CatchAll:
Type: Api
Properties:
Path: /positive
Method: GET
Environment:
Variables:
DYNAMODB_ENDPOINT: ''
DYNAMODB_TABLE_NAME: ''
PositiveLineBotTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: 'PositiveLineBotTable'
AttributeDefinitions:
- AttributeName: 'Id'
AttributeType: 'N'
KeySchema:
- AttributeName: 'Id'
KeyType: 'HASH'
ProvisionedThroughput:
ReadCapacityUnits: '5'
WriteCapacityUnits: '5'
BillingMode: PAY_PER_REQUEST
Outputs:
PositiveLineBotAPI:
Description: 'API Gateway endpoint URL for Prod environment for PositiveLineBot'
Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/positive/'
PositiveLineBotFunction:
Description: 'PositiveLineBot Lambda Function ARN'
Value: !GetAtt PositiveLineBotFunction.Arn
PositiveLineBotFunctionIamRole:
Description: 'Implicit IAM Role created for PositiveLineBot'
Value: !GetAtt PositiveLineBotFunction.Arn
Here is the full source code.
https://github.com/jpskgc/line-positive-bot
See this answer
Solutions consists of 2 parts:
Create a docker-network and start the dynamodb-local container and the api using that network
Adjust the endpoint appropriately.
For me, I did:
docker network create dynamodb-network
docker run -d -v "$PWD":/dynamodb_local_db -p 8000:8000 --network dynamodb-network --name dynamodb cnadiminti/dynamodb-local
sam local start-api --docker-network dynamodb-network -n env.json
and in my code I referenced the docker name as the DNS address:
const awsRegion = process.env.AWS_REGION || "us-east-2";
const options = {
region: awsRegion,
};
if (process.env.AWS_SAM_LOCAL) {
options.endpoint = "http://dynamodb:8000";
}
const docClient = new dynamodb.DocumentClient(options);

Set ObjectMeta on Istio-resource with go-client

I'm trying to work with Istio from Go, and are using Kubernetes and Istio go-client code.
The problem I'm having is that I can't specify ObjectMeta or TypeMeta in my Istio-ServiceRole object. I can only specify rules, which are inside the spec.
Below you can see what I got working:
import (
v1alpha1 "istio.io/api/rbac/v1alpha1"
)
func getDefaultServiceRole(app nais.Application) *v1alpha1.ServiceRole {
return &v1alpha1.ServiceRole{
Rules: []*v1alpha1.AccessRule{
{
Ports: []int32{2},
},
},
}
}
What I would like to do is have this code work:
func getDefaultServiceRole(app *nais.Application) *v1alpha1.ServiceRole {
return &v1alpha1.ServiceRole{
TypeMeta: metav1.TypeMeta{
Kind: "ServiceRole",
APIVersion: "v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: app.Name,
Namespace: app.Namespace,
},
Spec: v1alpha1.ServiceRole{
Rules: []*v1alpha1.AccessRule{
{
Ports: []int32{2},
},
},
},
},
}
Can anyone point me in the right direction?
Ah - this is a pretty painful point: Istio requires Kubernetes CRD wrapper metadata (primarily the name and namespace fields), but those fields are not part of the API objects themselves nor are they represented in the protos. (This is changing with the new MCP API for configuring components - which Galley uses - does encode these fields as protobufs but that doesn't help for your use case.) Instead, you should use the types in istio.io/istio/pilot/pkg/config/kube/crd, which implement the K8s CRD interface.
The easiest way to work with the Istio objects in golang is to use Pilot's libraries, particularly the istio.io/istio/pilot/pkg/model and istio.io/istio/pilot/pkg/config/kube/crd packages, as well as the model.Config struct. You can either pass around the full model.Config (not great because spec has type proto.Message so you need type assertions to extract the data you care about), or pass around the inner object wrap it in a model.Config before you push it. You can use the model.ProtoSchema type to help with conversion to and from YAML and JSON. Pilot only defines ProtoSchema objects for the networking API, the type is public and you can create them for arbitrary types.
So, using your example code I might try something like:
import (
v1alpha1 "istio.io/api/rbac/v1alpha1"
"istio.io/istio/pilot/pkg/model"
)
func getDefaultServiceRole() *v1alpha1.ServiceRole {
return &v1alpha1.ServiceRole{
Rules: []*v1alpha1.AccessRule{
{
Ports: []int32{2},
},
},
}
}
func toConfig(app *nais.Application, role *v1alpha1.ServiceRole) model.Config {
return &model.Config{
ConfigMeta: model.ConfigMeta{
Name: app.Name,
Namespace: app.Namespace,
},
Spec: app,
}
}
type Client model.ConfigStore
func (c Client) CreateRoleFor(app nais.Application, role *v1alpha1.ServiceRole) error {
cfg := toConfig(app, role)
_, err := c.Create(cfg)
return err
}
As a more complete example, we built the Istio CloudMap operator in this style. Here's the core of it that pushes config to K8s with Pilot libraries. Here's the incantation to create an instance of model.ConfigStore to use to create objects. Finally, I want to call out explicitly as it's only implicit in the example: when you call Create on the model.ConfigStore, the ConfigStore relies on the metadata in the ProtoSchema objects used to create it. So be sure to initialize the store with ProtoSchema objects for all of the types you'll be working with.
You can achieve the same using just the K8s client libraries and the istio.io/istio/pilot/pkg/config/kube/crd package, but I have not done it firsthand and don't have examples handy.
Istio now supports:
import (
istiov1alpha3 "istio.io/api/networking/v1alpha3"
istiogov1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3"
)
VirtualService := istiogov1alpha3.VirtualService{
TypeMeta: metav1.TypeMeta{
Kind: "VirtualService",
APIVersion: "networking.istio.io/v1alpha3",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-name",
},
Spec: istiov1alpha3.VirtualService{},
}
Where istiov1alpha3.VirtualService{} is an istio object.

Resources