├── scripts
├── header.go.txt
├── logs
├── all
├── push
├── test
├── refresh-mod
├── generate-profiles
├── format
├── nuke-namespace
├── install-bash-completion
├── pull
├── _env
├── install-tools
├── delete
├── publish-container-image
├── build
├── _trap
├── generate-apis
├── deploy
├── build-container-image
├── release
└── save-portable-container-image
├── CNAME
├── _config.yml
├── lab
├── linphone
│ ├── .gitignore
│ ├── _env
│ ├── linphone-central
│ ├── linphonec-edge
│ ├── linphonerc-central.template
│ └── linphonerc-edge.template
├── README.md
├── watch
├── openshift
│ ├── shell
│ ├── prepare
│ ├── README.md
│ └── test
├── minikube
│ ├── prepare
│ ├── prepare-multi
│ ├── test
│ └── test-multi
├── configure
└── connect-kube-dns
├── examples
├── helm
│ ├── artifacts
│ │ └── .gitignore
│ ├── hello-world
│ │ ├── templates
│ │ │ ├── serviceaccount.yaml
│ │ │ ├── service.yaml
│ │ │ ├── tests
│ │ │ │ └── test-connection.yaml
│ │ │ ├── hpa.yaml
│ │ │ ├── ingress.yaml
│ │ │ ├── NOTES.txt
│ │ │ ├── _helpers.tpl
│ │ │ └── deployment.yaml
│ │ ├── .helmignore
│ │ ├── Chart.yaml
│ │ └── values.yaml
│ ├── scripts
│ │ ├── build-chart
│ │ └── build-csar
│ ├── helm.yaml
│ └── README.md
├── self-contained
│ ├── artifacts
│ │ ├── .gitignore
│ │ ├── scripts
│ │ │ └── configure.sh
│ │ └── scriptlets
│ │ │ └── set-output.js
│ ├── scripts
│ │ ├── save-container-image
│ │ └── build-csar
│ └── README.md
├── telephony-network-service
│ ├── artifacts
│ │ ├── .gitignore
│ │ ├── asterisk
│ │ │ ├── rtp.conf
│ │ │ ├── extensions-cnf.conf.template
│ │ │ ├── extensions-vnf.conf.template
│ │ │ ├── pjsip-cnf.conf.template
│ │ │ └── pjsip-vnf.conf.template
│ │ ├── keypairs
│ │ │ ├── admin@asterisk-vnf.pub
│ │ │ └── admin@asterisk-vnf
│ │ ├── cloud-config
│ │ │ └── asterisk-vnf.yaml
│ │ └── scripts
│ │ │ └── asterisk
│ │ │ ├── configure-cnf.sh
│ │ │ └── configure-vnf.sh
│ ├── scripts
│ │ ├── ssh-vnf
│ │ ├── register-csars
│ │ ├── build-asterisk-cnf-container-image
│ │ ├── build-asterisk-vnf-container-image
│ │ └── build-csars
│ ├── profiles
│ │ ├── telephony
│ │ │ └── profile.yaml
│ │ └── network-service
│ │ │ └── profile.yaml
│ └── simple-data-plane.yaml
├── README.md
├── build-csars
└── hello-world
│ ├── README.md
│ ├── artifacts
│ ├── scripts
│ │ └── configure.sh
│ └── scriptlets
│ │ └── set-output.js
│ └── scripts
│ └── build-csar
├── turandot
├── README.md
├── commands
│ ├── version.go
│ ├── service.go
│ ├── delegate.go
│ ├── operator.go
│ ├── template.go
│ ├── operator-status.go
│ ├── operator-shell.go
│ ├── operator-uninstall.go
│ ├── operator-logs.go
│ ├── common.go
│ ├── service-clout.go
│ ├── shell.go
│ ├── operator-install.go
│ ├── template-pull.go
│ ├── service-output.go
│ ├── service-mode.go
│ ├── delegate-delete.go
│ ├── delegate-list.go
│ ├── logs.go
│ ├── service-delete.go
│ ├── delegate-set.go
│ ├── template-delist.go
│ ├── client.go
│ ├── template-register.go
│ ├── template-list.go
│ ├── service-list.go
│ └── root.go
└── main.go
├── .gitignore
├── resources
├── README.md
└── turandot.puccini.cloud
│ ├── common.go
│ └── v1alpha1
│ ├── doc.go
│ └── scheme.go
├── assets
├── media
│ ├── download.png
│ └── telephony-network-service.png
├── tosca
│ └── profiles
│ │ ├── kubernetes
│ │ ├── 1.0
│ │ │ ├── js
│ │ │ │ ├── converters
│ │ │ │ │ ├── percent.js
│ │ │ │ │ └── any.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── get-mappings.js
│ │ │ │ │ └── update-mappings.js
│ │ │ │ └── artifacts
│ │ │ │ │ ├── update.js
│ │ │ │ │ └── get.js
│ │ │ ├── relationships.yaml
│ │ │ ├── policies.yaml
│ │ │ ├── groups.yaml
│ │ │ ├── repositories.yaml
│ │ │ ├── nodes.yaml
│ │ │ ├── artifacts.yaml
│ │ │ ├── profile.yaml
│ │ │ └── interfaces.yaml
│ │ └── README.md
│ │ ├── mariadb
│ │ ├── profile.yaml
│ │ ├── nodes.yaml
│ │ └── capabilities.yaml
│ │ ├── ansible
│ │ └── 1.0
│ │ │ ├── profile.yaml
│ │ │ └── artifacts.yaml
│ │ ├── helm
│ │ └── 1.0
│ │ │ ├── artifacts.yaml
│ │ │ ├── profile.yaml
│ │ │ ├── nodes.yaml
│ │ │ └── js
│ │ │ └── kubernetes-resources-get.js
│ │ ├── kubevirt
│ │ └── 1.0
│ │ │ ├── profile.yaml
│ │ │ ├── artifacts.yaml
│ │ │ └── js
│ │ │ └── kubernetes-resources-pre-get.js
│ │ └── orchestration
│ │ └── 1.0
│ │ ├── profile.yaml
│ │ ├── js
│ │ ├── states
│ │ │ ├── reset.js
│ │ │ ├── get.js
│ │ │ └── set.js
│ │ └── policies.js
│ │ ├── data.yaml
│ │ ├── artifacts.yaml
│ │ ├── interfaces.yaml
│ │ └── policies.yaml
├── kubernetes
│ ├── cluster-mode-authorization.yaml
│ ├── hello-world.yaml
│ └── custom-resource-definition.yaml
└── profile-generator
│ └── kubevirt.yaml
├── NOTICE
├── apis
├── clientset
│ └── versioned
│ │ ├── fake
│ │ ├── doc.go
│ │ ├── register.go
│ │ └── clientset_generated.go
│ │ ├── scheme
│ │ ├── doc.go
│ │ └── register.go
│ │ └── typed
│ │ └── turandot.puccini.cloud
│ │ └── v1alpha1
│ │ ├── fake
│ │ ├── doc.go
│ │ └── fake_turandot.puccini.cloud_client.go
│ │ ├── generated_expansion.go
│ │ └── doc.go
├── listers
│ └── turandot.puccini.cloud
│ │ └── v1alpha1
│ │ └── expansion_generated.go
├── informers
│ └── externalversions
│ │ ├── internalinterfaces
│ │ └── factory_interfaces.go
│ │ ├── turandot.puccini.cloud
│ │ ├── v1alpha1
│ │ │ └── interface.go
│ │ └── interface.go
│ │ └── generic.go
└── applyconfiguration
│ ├── internal
│ └── internal.go
│ ├── turandot.puccini.cloud
│ └── v1alpha1
│ │ ├── servicetemplate.go
│ │ ├── servicetemplateindirect.go
│ │ ├── servicenodemodestate.go
│ │ ├── servicespec.go
│ │ └── servicetemplatedirect.go
│ └── utils.go
├── turandot-profile-generator
├── main.go
├── command.go
├── configuration.go
├── version.go
├── parse.go
└── generator.go
├── controller
├── common.go
├── README.md
├── parser
│ ├── outputs.go
│ ├── kubernetes-artifact.go
│ ├── orchestration-state.go
│ ├── clout-attribute-value.go
│ └── kubernetes-resource-mapping.go
├── tosca.go
├── policy.go
├── events.go
├── delegation.go
├── substitution.go
├── artifact.go
└── puccini.go
├── turandot-operator
├── common.go
├── main.go
└── controller.go
├── client
├── rest.go
├── tls.go
└── registry.go
└── .goreleaser.yml
/scripts/header.go.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | turandot.puccini.cloud
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/lab/linphone/.gitignore:
--------------------------------------------------------------------------------
1 | linphonerc-edge
2 |
--------------------------------------------------------------------------------
/examples/helm/artifacts/.gitignore:
--------------------------------------------------------------------------------
1 | charts/
2 |
--------------------------------------------------------------------------------
/lab/README.md:
--------------------------------------------------------------------------------
1 | Turandot Lab
2 | ============
3 |
--------------------------------------------------------------------------------
/turandot/README.md:
--------------------------------------------------------------------------------
1 | turandot
2 | --------
3 |
4 |
--------------------------------------------------------------------------------
/examples/self-contained/artifacts/.gitignore:
--------------------------------------------------------------------------------
1 | images/
2 |
--------------------------------------------------------------------------------
/lab/linphone/_env:
--------------------------------------------------------------------------------
1 |
2 | LIBVIRT_GATEWAY=192.168.39.1
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | go.work
3 | go.work.sum
4 |
5 | dist/
6 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/.gitignore:
--------------------------------------------------------------------------------
1 | images/
2 | binaries/
3 |
--------------------------------------------------------------------------------
/resources/README.md:
--------------------------------------------------------------------------------
1 |
2 | The directory structure is:
3 |
4 | [group]/[version]
5 |
--------------------------------------------------------------------------------
/assets/media/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tliron/turandot/HEAD/assets/media/download.png
--------------------------------------------------------------------------------
/assets/media/telephony-network-service.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tliron/turandot/HEAD/assets/media/telephony-network-service.png
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Turandot
2 | Copyright 2020-2024 Tal Liron
3 |
4 | ---
5 |
6 | "Kubernetes" and "K8s" are registered trademarks of The Linux Foundation.
7 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/js/converters/percent.js:
--------------------------------------------------------------------------------
1 |
2 | exports.convert = function(value) {
3 | return (value * 100) + '%';
4 | };
5 |
--------------------------------------------------------------------------------
/lab/watch:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CONTEXT=${1:-central}
4 |
5 | watch --color "$(which turandot)" service list --colorize=force --context="$CONTEXT"
6 |
--------------------------------------------------------------------------------
/resources/turandot.puccini.cloud/common.go:
--------------------------------------------------------------------------------
1 | package puccini
2 |
3 | // Kubernetes API group name (must have a ".")
4 | const GroupName = "turandot.puccini.cloud"
5 |
--------------------------------------------------------------------------------
/apis/clientset/versioned/fake/doc.go:
--------------------------------------------------------------------------------
1 | // Code generated by client-gen. DO NOT EDIT.
2 |
3 | // This package has the automatically generated fake clientset.
4 | package fake
5 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/mariadb/profile.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | namespace: cloud.puccini.mariadb
4 |
5 | imports:
6 |
7 | - nodes.yaml
8 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/ansible/1.0/profile.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | namespace: cloud.puccini.ansible
4 |
5 | imports:
6 |
7 | - artifacts.yaml
8 |
--------------------------------------------------------------------------------
/apis/clientset/versioned/scheme/doc.go:
--------------------------------------------------------------------------------
1 | // Code generated by client-gen. DO NOT EDIT.
2 |
3 | // This package contains the scheme of the automatically generated clientset.
4 | package scheme
5 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/ansible/1.0/artifacts.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | artifact_types:
4 |
5 | Playbook:
6 | description: >-
7 | Ansible playbook
8 |
--------------------------------------------------------------------------------
/apis/clientset/versioned/typed/turandot.puccini.cloud/v1alpha1/fake/doc.go:
--------------------------------------------------------------------------------
1 | // Code generated by client-gen. DO NOT EDIT.
2 |
3 | // Package fake has the automatically generated clients.
4 | package fake
5 |
--------------------------------------------------------------------------------
/apis/clientset/versioned/typed/turandot.puccini.cloud/v1alpha1/generated_expansion.go:
--------------------------------------------------------------------------------
1 | // Code generated by client-gen. DO NOT EDIT.
2 |
3 | package v1alpha1
4 |
5 | type ServiceExpansion interface{}
6 |
--------------------------------------------------------------------------------
/turandot-profile-generator/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/tliron/kutil/util"
5 | )
6 |
7 | func main() {
8 | err := command.Execute()
9 | util.FailOnError(err)
10 | }
11 |
--------------------------------------------------------------------------------
/apis/clientset/versioned/typed/turandot.puccini.cloud/v1alpha1/doc.go:
--------------------------------------------------------------------------------
1 | // Code generated by client-gen. DO NOT EDIT.
2 |
3 | // This package has the automatically generated typed clients.
4 | package v1alpha1
5 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | Examples
2 | ========
3 |
4 | * [Hello World](hello-world/)
5 | * [Self-Contained](self-contained/)
6 | * [Helm](helm/)
7 | * [Telephony Network Service (MANO)](telephony-network-service/)
8 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/asterisk/rtp.conf:
--------------------------------------------------------------------------------
1 | ; https://www.voip-info.org/asterisk-config-rtpconf/
2 |
3 | [general]
4 | rtpstart=10000
5 | rtpend=10007
6 | ;rtpchecksums=no
7 | ;strictrtp=no
8 |
--------------------------------------------------------------------------------
/lab/openshift/shell:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../scripts/_env"
6 |
7 | NODE=$(oc get node --output name)
8 | oc debug "$NODE"
9 |
--------------------------------------------------------------------------------
/turandot/commands/version.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/tliron/kutil/cobra"
5 | )
6 |
7 | func init() {
8 | rootCommand.AddCommand(cobra.NewVersionCommand(toolName))
9 | }
10 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/helm/1.0/artifacts.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | artifact_types:
4 |
5 | Chart:
6 | description: >-
7 | Helm chart.
8 | file_ext: [ tar, tar.gz, tgz ]
9 |
--------------------------------------------------------------------------------
/scripts/logs:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 |
7 | POD=$(kubectl_first_pod puccini-kubernetes-operator)
8 | kubectl logs "$POD" --namespace="$WORKSPACE" "$@"
9 |
--------------------------------------------------------------------------------
/resources/turandot.puccini.cloud/v1alpha1/doc.go:
--------------------------------------------------------------------------------
1 | package v1alpha1
2 |
3 | // Note: Generator *requires* file to be called "doc.go"
4 |
5 | // +k8s:deepcopy-gen=package
6 | // +groupName=turandot.puccini.cloud
7 | const Version = "v1alpha1"
8 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/relationships.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | imports:
4 |
5 | - capabilities.yaml
6 |
7 | relationship_types:
8 |
9 | Route:
10 | valid_target_types: [ Service ] # capability
11 |
--------------------------------------------------------------------------------
/controller/common.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | const (
4 | NamePrefix = "turandot"
5 | PartOf = "Turandot"
6 | ManagedBy = "Turandot"
7 | OperatorImageName = "tliron/turandot-operator"
8 | CacheDirectory = "/cache"
9 | )
10 |
--------------------------------------------------------------------------------
/scripts/all:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | "$HERE/build-container-image"
9 | "$HERE/publish-container-image"
10 | "$HERE/deploy-operator" -c
11 | "$HERE/logs" -f
12 |
--------------------------------------------------------------------------------
/turandot-operator/common.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | contextpkg "context"
5 |
6 | "github.com/tliron/commonlog"
7 | )
8 |
9 | const toolName = "turandot-operator"
10 |
11 | var context = contextpkg.TODO()
12 |
13 | var log = commonlog.GetLogger(toolName)
14 |
--------------------------------------------------------------------------------
/scripts/push:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 |
7 | FROM=$1
8 | TO=$2
9 |
10 | POD=$(kubectl_first_pod puccini-kubernetes-operator)
11 |
12 | kubectl cp "$FROM" "$POD:$TO" --namespace="$WORKSPACE"
13 |
--------------------------------------------------------------------------------
/turandot/commands/service.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | func init() {
8 | rootCommand.AddCommand(serviceCommand)
9 | }
10 |
11 | var serviceCommand = &cobra.Command{
12 | Use: "service",
13 | Short: "Work with services",
14 | }
15 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/js/converters/any.js:
--------------------------------------------------------------------------------
1 |
2 | exports.convert = function(value) {
3 | for (let name in value) {
4 | let field = value[name];
5 | if (field !== undefined) {
6 | return field;
7 | }
8 | }
9 | return null;
10 | };
11 |
--------------------------------------------------------------------------------
/turandot/commands/delegate.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | func init() {
8 | rootCommand.AddCommand(delegateCommand)
9 | }
10 |
11 | var delegateCommand = &cobra.Command{
12 | Use: "delegate",
13 | Short: "Work with delegates",
14 | }
15 |
--------------------------------------------------------------------------------
/turandot/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/tliron/kutil/util"
5 | "github.com/tliron/turandot/turandot/commands"
6 |
7 | _ "github.com/tliron/commonlog/simple"
8 | )
9 |
10 | func main() {
11 | util.ExitOnSignals()
12 | commands.Execute()
13 | util.Exit(0)
14 | }
15 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/policies.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | # https://istio.io/docs/reference/config/istio.networking.v1alpha3.html
4 |
5 | policy_types:
6 |
7 | TrafficPolicy:
8 | description: >-
9 | DestinationRule (with TrafficPolicy)
10 |
--------------------------------------------------------------------------------
/turandot/commands/operator.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | func init() {
8 | rootCommand.AddCommand(operatorCommand)
9 | }
10 |
11 | var operatorCommand = &cobra.Command{
12 | Use: "operator",
13 | Short: "Control the Turandot operator",
14 | }
15 |
--------------------------------------------------------------------------------
/turandot/commands/template.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | func init() {
8 | rootCommand.AddCommand(templateCommand)
9 | }
10 |
11 | var templateCommand = &cobra.Command{
12 | Use: "template",
13 | Short: "Work with service templates in the repository",
14 | }
15 |
--------------------------------------------------------------------------------
/examples/build-csars:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../scripts/_env"
6 |
7 | "$HERE/hello-world/scripts/build-csar"
8 | "$HERE/self-contained/scripts/build-csar"
9 | "$HERE/helm/scripts/build-csar"
10 | "$HERE/telephony-network-service/scripts/build-csars"
11 |
--------------------------------------------------------------------------------
/turandot-operator/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/tliron/kutil/util"
5 |
6 | _ "github.com/tliron/commonlog/simple"
7 | _ "k8s.io/client-go/plugin/pkg/client/auth" // load all auth plugins
8 | )
9 |
10 | func main() {
11 | err := command.Execute()
12 | util.FailOnError(err)
13 | util.Exit(0)
14 | }
15 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubevirt/1.0/profile.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | metadata:
4 |
5 | puccini.scriptlet.import:kubernetes.plugins.resources.pre-get.kubevirt: js/kubernetes-resources-pre-get.js
6 |
7 | namespace: cloud.puccini.kubevirt
8 |
9 | imports:
10 |
11 | - capabilities.yaml
12 | - artifacts.yaml
13 |
--------------------------------------------------------------------------------
/scripts/test:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | "$HERE/build"
9 |
10 | # -count=1 is the idiomatic way to disable test caching
11 |
12 | m 'testing...'
13 |
14 | #ROOT=$ROOT \
15 | #go test -count=1 github.com/tliron/puccini/puccini-tosca "$@"
16 |
--------------------------------------------------------------------------------
/scripts/refresh-mod:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | rm --force "$ROOT/go.mod" "$ROOT/go.sum"
9 |
10 | cd "$ROOT"
11 | go mod init "$MODULE"
12 | go get k8s.io/client-go@v$K8S_VERSION
13 | go mod tidy
14 |
15 | "$HERE/test"
16 |
17 | go mod tidy
18 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/helm/1.0/profile.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | metadata:
4 |
5 | puccini.scriptlet.import:kubernetes.plugins.resources.get.helm: js/kubernetes-resources-get.js
6 |
7 | namespace: cloud.puccini.helm
8 |
9 | imports:
10 |
11 | - nodes.yaml
12 | - artifacts.yaml
13 | - ../../kubernetes/1.0/profile.yaml
14 |
--------------------------------------------------------------------------------
/examples/hello-world/README.md:
--------------------------------------------------------------------------------
1 | Hello World Example
2 | ===================
3 |
4 | A stateless single-pod web workload comprising a deployment and a loadbalancer service.
5 |
6 | Follow the [tutorial](../../TUTORIAL.md) for complete instructions on how to deploy it.
7 |
8 |
9 | Helper Scripts
10 | --------------
11 |
12 | * [Package as CSAR file](scripts/build-csar)
13 |
--------------------------------------------------------------------------------
/scripts/generate-profiles:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | go install "$ROOT/turandot-profile-generator"
9 |
10 | turandot-profile-generator "$ROOT/assets/profile-generator/kubernetes.yaml"
11 | turandot-profile-generator "$ROOT/assets/profile-generator/kubevirt.yaml"
12 |
--------------------------------------------------------------------------------
/assets/kubernetes/cluster-mode-authorization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRoleBinding
3 |
4 | metadata:
5 | name: turandot
6 |
7 | subjects:
8 | - kind: ServiceAccount
9 | name: turandot
10 | namespace: !!string $NAMESPACE # required
11 |
12 | roleRef:
13 | apiGroup: rbac.authorization.k8s.io
14 | kind: ClusterRole
15 | name: cluster-admin
16 |
--------------------------------------------------------------------------------
/examples/hello-world/artifacts/scripts/configure.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | if [ -f /tmp/configured ]; then
5 | exit 0
6 | fi
7 |
8 | NODE_TEMPLATE=$1
9 |
10 | if ! grep -qF TOSCA /usr/src/app/views/home.handlebars; then
11 | echo "
Part of TOSCA node: \"$NODE_TEMPLATE\"
" >> /usr/src/app/views/home.handlebars
12 | fi
13 |
14 | touch /tmp/configured
15 |
--------------------------------------------------------------------------------
/examples/self-contained/artifacts/scripts/configure.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | if [ -f /tmp/configured ]; then
5 | exit 0
6 | fi
7 |
8 | NODE_TEMPLATE=$1
9 |
10 | if ! grep -qF TOSCA /usr/src/app/views/home.handlebars; then
11 | echo "Part of TOSCA node: \"$NODE_TEMPLATE\"
" >> /usr/src/app/views/home.handlebars
12 | fi
13 |
14 | touch /tmp/configured
15 |
--------------------------------------------------------------------------------
/turandot/commands/operator-status.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | func init() {
8 | operatorCommand.AddCommand(operatorStatusCommand)
9 | }
10 |
11 | var operatorStatusCommand = &cobra.Command{
12 | Use: "status",
13 | Short: "Show the status of the Turandot operator",
14 | Run: func(cmd *cobra.Command, args []string) {
15 | },
16 | }
17 |
--------------------------------------------------------------------------------
/lab/minikube/prepare:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | minikube profile central
9 |
10 | "$HERE/../cert-manager/deploy"
11 | "$HERE/../multus/deploy"
12 | "$HERE/../kubevirt/deploy"
13 |
14 | kubectl create namespace workspace || true
15 |
16 | "$HERE/../mariadb/deploy"
17 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/groups.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | imports:
4 |
5 | - nodes.yaml
6 |
7 | group_types:
8 |
9 | Namespace:
10 | description: >-
11 | Will automatically use a "group" label (the name of the group) for all deployment controllers.
12 | members:
13 | - Service
14 | properties:
15 | namespace:
16 | type: string
17 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/scripts/ssh-vnf:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 |
6 | KEY=$HERE/../artifacts/keypairs/admin@asterisk-vnf
7 |
8 | IP=$(turandot service output asterisk-vnf tcp-ip)
9 |
10 | ssh \
11 | -i "$KEY" \
12 | -o StrictHostKeyChecking=no \
13 | -o UserKnownHostsFile=/dev/null \
14 | -o LogLevel=ERROR \
15 | "admin@$IP" \
16 | "$@"
17 |
--------------------------------------------------------------------------------
/turandot/commands/operator-shell.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | func init() {
8 | operatorCommand.AddCommand(operatorShellCommand)
9 | }
10 |
11 | var operatorShellCommand = &cobra.Command{
12 | Use: "shell",
13 | Short: "Opens a shell to the Turandot operator",
14 | Run: func(cmd *cobra.Command, args []string) {
15 | Shell("operator", "operator")
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/templates/serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.serviceAccount.create -}}
2 | apiVersion: v1
3 | kind: ServiceAccount
4 | metadata:
5 | name: {{ include "hello-world.serviceAccountName" . }}
6 | labels:
7 | {{- include "hello-world.labels" . | nindent 4 }}
8 | {{- with .Values.serviceAccount.annotations }}
9 | annotations:
10 | {{- toYaml . | nindent 4 }}
11 | {{- end }}
12 | {{- end }}
13 |
--------------------------------------------------------------------------------
/lab/linphone/linphone-central:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../scripts/_env"
6 | . "$HERE/_env"
7 |
8 | minikube profile central
9 |
10 | EXTERNAL_IP=$(turandot service output asterisk-vnf udp-ip -n workspace)
11 |
12 | cat "$HERE/linphonerc-central.template" | \
13 | IP=$EXTERNAL_IP NAT_IP=$LIBVIRT_GATEWAY envsubst > ~/.linphonerc
14 |
15 | linphone
16 |
--------------------------------------------------------------------------------
/apis/listers/turandot.puccini.cloud/v1alpha1/expansion_generated.go:
--------------------------------------------------------------------------------
1 | // Code generated by lister-gen. DO NOT EDIT.
2 |
3 | package v1alpha1
4 |
5 | // ServiceListerExpansion allows custom methods to be added to
6 | // ServiceLister.
7 | type ServiceListerExpansion interface{}
8 |
9 | // ServiceNamespaceListerExpansion allows custom methods to be added to
10 | // ServiceNamespaceLister.
11 | type ServiceNamespaceListerExpansion interface{}
12 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubevirt/1.0/artifacts.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | artifact_types:
4 |
5 | CloudConfig:
6 | description: >-
7 | Configuration for cloud-init
8 | file_ext: [ yaml, yml ]
9 | properties:
10 | base64:
11 | type: boolean
12 | default: false
13 | variables:
14 | type: map
15 | entry_schema: string
16 | required: false
17 |
--------------------------------------------------------------------------------
/examples/hello-world/artifacts/scriptlets/set-output.js:
--------------------------------------------------------------------------------
1 |
2 | const tosca = require('tosca.lib.utils');
3 |
4 | let nodeTemplateName = puccini.arguments.nodeTemplate;
5 | let name = puccini.arguments.name;
6 | let value = puccini.arguments.value;
7 |
8 | puccini.log.infof('execution for node template %q, setting output: %s -> %s', nodeTemplateName, name, value);
9 |
10 | if (tosca.setOutputValue(name, value))
11 | puccini.write(clout);
12 |
--------------------------------------------------------------------------------
/lab/openshift/prepare:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | minikube profile edge
9 |
10 | "$HERE/../multus/deploy"
11 |
12 | oc config use-context crc-admin
13 |
14 | "$HERE/../multus/deploy"
15 | "$HERE/../kubevirt/deploy"
16 |
17 | kubectl create namespace workspace || true
18 |
19 | "$HERE/../mariadb/deploy"
20 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/js/resources/get-mappings.js:
--------------------------------------------------------------------------------
1 |
2 | let mappings = {};
3 |
4 | for (let vertexId in clout.vertexes) {
5 | let vertex = clout.vertexes[vertexId];
6 | if (!vertex.metadata.turandot ||
7 | (vertex.metadata.turandot.version !== '1.0'))
8 | continue;
9 |
10 | if (vertex.metadata.turandot.resources)
11 | mappings[vertexId] = vertex.metadata.turandot.resources;
12 | }
13 |
14 | puccini.write(mappings);
15 |
--------------------------------------------------------------------------------
/examples/helm/scripts/build-chart:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | CHARTS=$(readlink --canonicalize "$HERE/../artifacts/charts")
9 |
10 | mkdir --parents "$CHARTS"
11 |
12 | rm --force "$CHARTS/hello-world.tar.gz"
13 | tar --create --gzip --file="$CHARTS/hello-world.tar.gz" --directory="$HERE/../hello-world" .
14 |
--------------------------------------------------------------------------------
/examples/self-contained/artifacts/scriptlets/set-output.js:
--------------------------------------------------------------------------------
1 |
2 | const tosca = require('tosca.lib.utils');
3 |
4 | let nodeTemplateName = puccini.arguments.nodeTemplate;
5 | let name = puccini.arguments.name;
6 | let value = puccini.arguments.value;
7 |
8 | puccini.log.infof('execution for node template %q, setting output: %s -> %s', nodeTemplateName, name, value);
9 |
10 | if (tosca.setOutputValue(name, value))
11 | puccini.write(clout);
12 |
--------------------------------------------------------------------------------
/scripts/format:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 |
7 | gofmt -w -s -e \
8 | "$ROOT/client" \
9 | "$ROOT/controller" \
10 | "$ROOT/controller/parser" \
11 | "$ROOT/resources" \
12 | "$ROOT/resources/turandot.puccini.cloud" \
13 | "$ROOT/resources/turandot.puccini.cloud/v1alpha1" \
14 | "$ROOT/turandot" \
15 | "$ROOT/turandot/commands" \
16 | "$ROOT/turandot-operator"
--------------------------------------------------------------------------------
/scripts/nuke-namespace:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 |
7 | # https://medium.com/@clouddev.guru/how-to-fix-kubernetes-namespace-deleting-stuck-in-terminating-state-5ed75792647e
8 |
9 | NAMESPACE=$1
10 |
11 | kubectl get namespaces "$NAMESPACE" --output=json | \
12 | jq .spec.finalizers=[] | \
13 | kubectl replace --raw="/api/v1/namespaces/$NAMESPACE/finalize" -f -
14 |
--------------------------------------------------------------------------------
/controller/README.md:
--------------------------------------------------------------------------------
1 | https://github.com/kubernetes/sample-controller
2 | https://github.com/kubernetes/kubernetes/tree/master/staging/src/k8s.io/sample-controller
3 | https://github.com/kubernetes/community/blob/8cafef897a22026d42f5e5bb3f104febe7e29830/contributors/devel/controllers.md
4 | https://engineering.bitnami.com/articles/a-deep-dive-into-kubernetes-controllers.html
5 | https://github.com/operator-framework/community-operators/blob/master/docs/best-practices.md
6 |
--------------------------------------------------------------------------------
/lab/linphone/linphonec-edge:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../scripts/_env"
6 | . "$HERE/_env"
7 |
8 | minikube profile edge
9 |
10 | EXTERNAL_IP=$(turandot service output asterisk-cnf udp-ip -n workspace)
11 |
12 | cat "$HERE/linphonerc-edge.template" | \
13 | IP=$EXTERNAL_IP NAT_IP=$LIBVIRT_GATEWAY envsubst > "$HERE/linphonerc-edge"
14 |
15 | linphonec -c "$HERE/linphonerc-edge"
16 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/repositories.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | repositories:
4 |
5 | docker-hub:
6 | description: >-
7 | Docker Hub
8 | url: docker.io
9 |
10 | quay:
11 | description: >-
12 | Quay
13 | url: quay.io
14 |
15 | red-hat:
16 | url: registry.redhat.io
17 |
18 | centos:
19 | url: registry.centos.org
20 |
21 | fedora:
22 | url: registry.fedoraproject.org
23 |
--------------------------------------------------------------------------------
/lab/configure:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | cd "$ROOT/../telephony-network-service"
9 |
10 | minikube profile central
11 |
12 | EXTERNAL_IP=$(kubectl_external_ip asterisk-vnf-tcp)
13 | IP=$EXTERNAL_IP \
14 | ssh/update
15 |
16 | data-plane/configure
17 | asterisk/vnf/configure
18 | asterisk/cnf/configure
19 |
20 | minikube profile central
21 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/templates/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ include "hello-world.fullname" . }}
5 | labels:
6 | {{- include "hello-world.labels" . | nindent 4 }}
7 | spec:
8 | type: {{ .Values.service.type }}
9 | ports:
10 | - port: {{ .Values.service.port }}
11 | targetPort: http
12 | protocol: TCP
13 | name: http
14 | selector:
15 | {{- include "hello-world.selectorLabels" . | nindent 4 }}
16 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/scripts/install-bash-completion:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # See: https://github.com/scop/bash-completion/blob/master/README.md
5 |
6 | USER_DIR=$BASH_COMPLETION_USER_DIR
7 |
8 | if [ -z "$USER_DIR" ]; then
9 | DATA_HOME=$XDG_DATA_HOME
10 | if [ -z "$DATA_HOME" ]; then
11 | DATA_HOME=~/.local/share
12 | fi
13 | USER_DIR=$DATA_HOME/bash-completion
14 | fi
15 |
16 | mkdir --parents "$USER_DIR/completions"
17 | turandot completion bash > "$USER_DIR/completions/turandot"
18 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/scripts/register-csars:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | function register () {
9 | local NAME=$1
10 | turandot template register "$NAME" --file="$ROOT/dist/$NAME.csar" --namespace=workspace
11 | }
12 |
13 | register telephony-network-service
14 | register asterisk-cnf
15 | register asterisk-vnf
16 | register simple-data-plane
17 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/asterisk/extensions-cnf.conf.template:
--------------------------------------------------------------------------------
1 | [default]
2 |
3 | exten => _X.,1,Log(NOTICE, $__{CALLERID(num)} called $__{EXTEN})
4 | same => n,Dial(PJSIP/$__{EXTEN})
5 | same => n,Dial(PJSIP/trunk/sip:$__{EXTEN}@$TRUNK_IP)
6 | same => n,Answer()
7 | same => n,Playback(im-sorry)
8 | same => n,Playback(extension)
9 | same => n,SayDigits($__{EXTEN})
10 | same => n,Playback(vm-isunavail)
11 | same => n,Playback(cannot-complete-as-dialed)
12 | same => n,Hangup()
13 |
--------------------------------------------------------------------------------
/scripts/pull:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 |
7 | FROM=$1
8 | TO=$2
9 |
10 | if [ -z "$TO" ]; then
11 | TO=$(basename "$FROM")
12 | elif [ -d "$TO" ]; then
13 | TO=$TO/$(basename "$FROM")
14 | elif [ "${TO: -1}" == / ]; then
15 | mkdir --parents "$TO"
16 | TO=$TO$(basename "$FROM")
17 | fi
18 |
19 | POD=$(kubectl_first_pod puccini-kubernetes-operator)
20 |
21 | kubectl cp "$POD:$FROM" "$TO" --namespace="$WORKSPACE"
22 |
--------------------------------------------------------------------------------
/lab/linphone/linphonerc-central.template:
--------------------------------------------------------------------------------
1 | [net]
2 | firewall_policy=1
3 | nat_address=$NAT_IP
4 |
5 | [sip]
6 | sip_port=5063
7 | default_proxy=0
8 |
9 | [rtp]
10 | audio_rtp_port=7081
11 | video_rtp_port=9081
12 |
13 | [proxy_0]
14 | reg_identity=sip:201@$IP
15 | reg_proxy=
16 | reg_sendregister=1
17 |
18 | [auth_info_0]
19 | username=201
20 | passwd=password
21 |
22 | [sound]
23 | playback_dev_id=PulseAudio: default
24 | ringer_dev_id=PulseAudio: default
25 | capture_dev_id=PulseAudio: default
26 |
--------------------------------------------------------------------------------
/lab/linphone/linphonerc-edge.template:
--------------------------------------------------------------------------------
1 | [net]
2 | firewall_policy=1
3 | nat_address=$NAT_IP
4 |
5 | [sip]
6 | sip_port=5061
7 | default_proxy=0
8 |
9 | [rtp]
10 | audio_rtp_port=7079
11 | video_rtp_port=9079
12 |
13 | [proxy_0]
14 | reg_identity=sip:100@$IP
15 | reg_proxy=
16 | reg_sendregister=1
17 |
18 | [auth_info_0]
19 | username=100
20 | passwd=password
21 |
22 | [sound]
23 | playback_dev_id=PulseAudio: default
24 | ringer_dev_id=PulseAudio: default
25 | capture_dev_id=PulseAudio: default
26 |
--------------------------------------------------------------------------------
/lab/minikube/prepare-multi:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | minikube profile edge
9 |
10 | "$HERE/../cert-manager/deploy"
11 | "$HERE/../multus/deploy"
12 |
13 | minikube profile central
14 |
15 | "$HERE/../cert-manager/deploy"
16 | "$HERE/../multus/deploy"
17 | "$HERE/../kubevirt/deploy"
18 |
19 | kubectl create namespace workspace || true
20 |
21 | "$HERE/../mariadb/deploy"
22 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/templates/tests/test-connection.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: "{{ include "hello-world.fullname" . }}-test-connection"
5 | labels:
6 | {{- include "hello-world.labels" . | nindent 4 }}
7 | annotations:
8 | "helm.sh/hook": test
9 | spec:
10 | containers:
11 | - name: wget
12 | image: busybox
13 | command: ['wget']
14 | args: ['{{ include "hello-world.fullname" . }}:{{ .Values.service.port }}']
15 | restartPolicy: Never
16 |
--------------------------------------------------------------------------------
/turandot-profile-generator/command.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/tliron/kutil/util"
6 | )
7 |
8 | var command = &cobra.Command{
9 | Use: "turandot-profile-generator [CONFIGURATION PATH]",
10 | Short: "Turandot Profile Generator",
11 | Args: cobra.ExactArgs(1),
12 | Run: func(cmd *cobra.Command, args []string) {
13 | generator, err := NewGenerator(args[0])
14 | util.FailOnError(err)
15 |
16 | err = generator.Generate()
17 | util.FailOnError(err)
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/examples/self-contained/scripts/save-container-image:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | # See: https://github.com/paulbouwer/hello-kubernetes
9 |
10 | IMAGE=docker.io/paulbouwer/hello-kubernetes:1.8
11 | IMAGES=$(readlink --canonicalize "$HERE/../artifacts/images")
12 |
13 | mkdir --parents "$IMAGES"
14 |
15 | podman pull "$IMAGE"
16 | "$ROOT/scripts/save-portable-container-image" "$IMAGE" "$IMAGES/hello-world.tar.gz"
17 |
--------------------------------------------------------------------------------
/scripts/_env:
--------------------------------------------------------------------------------
1 |
2 | _HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
3 |
4 | . "$_HERE/_functions"
5 |
6 | MODULE=github.com/tliron/turandot
7 |
8 | K8S_VERSION=0.27.3
9 | K8S_API_VERSION=1.27
10 | HELM_VERSION=3.12.1
11 | YQ_VERSION=4.34.1
12 |
13 | ROOT=$(readlink --canonicalize "$_HERE/..")
14 |
15 | GOPATH=${GOPATH:-$HOME/go}
16 | export PATH=$GOPATH/bin:$PATH
17 |
18 | WORKSPACE=${WORKSPACE:-workspace}
19 |
20 | if [ -d /Depot/Temporary ]; then
21 | export TMPDIR=/Depot/Temporary
22 | else
23 | export TMPDIR=/tmp
24 | fi
25 |
--------------------------------------------------------------------------------
/scripts/install-tools:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | if [ "$EUID" -ne 0 ]; then
5 | echo "Run this script as root"
6 | exit 1
7 | fi
8 |
9 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
10 | . "$HERE/_env"
11 |
12 | FORCE=false
13 | if [ "$1" == -f ]; then
14 | FORCE=true
15 | fi
16 |
17 | install_tool helm "$HELM_VERSION" \
18 | "https://get.helm.sh/helm-v$HELM_VERSION-linux-amd64.tar.gz" 1 \
19 | "linux-amd64/"
20 |
21 | install_tool yq "$YQ_VERSION" \
22 | "https://github.com/mikefarah/yq/releases/download/v$YQ_VERSION/yq_linux_amd64"
23 |
--------------------------------------------------------------------------------
/turandot/commands/operator-uninstall.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | func init() {
8 | operatorCommand.AddCommand(operatorUninstallCommand)
9 | operatorUninstallCommand.Flags().BoolVarP(&wait, "wait", "w", false, "wait for uninstallation to succeed")
10 | }
11 |
12 | var operatorUninstallCommand = &cobra.Command{
13 | Use: "uninstall",
14 | Short: "Uninstall the Turandot operator",
15 | Run: func(cmd *cobra.Command, args []string) {
16 | NewClient().Turandot().UninstallOperator(wait)
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/orchestration/1.0/profile.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | metadata:
4 |
5 | puccini.scriptlet.import:orchestration.policies: js/policies.js
6 | puccini.scriptlet.import:orchestration.states.get: js/states/get.js
7 | puccini.scriptlet.import:orchestration.states.set: js/states/set.js
8 | puccini.scriptlet.import:orchestration.states.reset: js/states/reset.js
9 |
10 | namespace: cloud.puccini.turandot.orchestration
11 |
12 | imports:
13 |
14 | - policies.yaml
15 | - interfaces.yaml
16 | - artifacts.yaml
17 |
--------------------------------------------------------------------------------
/examples/self-contained/README.md:
--------------------------------------------------------------------------------
1 | Self-Contained Example
2 | ======================
3 |
4 | A stateless single-pod web workload comprising a deployment and a loadbalancer service.
5 |
6 | This example demonstrates how a container image can be included as an artifact within a CSAR.
7 |
8 | Follow the [tutorial](../../TUTORIAL-SELF-CONTAINED.md) for complete instructions
9 | on how to deploy it.
10 |
11 |
12 | Helper Scripts
13 | --------------
14 |
15 | * [Save container images as tarball](scripts/save-container-image)
16 | * [Package as CSAR file](scripts/build-csar)
17 |
--------------------------------------------------------------------------------
/scripts/delete:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | export VERSION=1.0
9 |
10 | m "deleting operator from namespace \"$WORKSPACE\"..."
11 |
12 | kubectl_delete_template "$ROOT/assets/hello-world.yaml"
13 | kubectl_delete_template "$ROOT/assets/turandot.yaml"
14 | kubectl_delete_template "$ROOT/assets/cluster-mode-authorization.yaml"
15 | kubectl_delete_template "$ROOT/assets/namespace.yaml"
16 | kubectl_delete_template "$ROOT/assets/custom-resource-definitions.yaml"
17 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/js/resources/update-mappings.js:
--------------------------------------------------------------------------------
1 |
2 | const tosca = require('tosca.lib.utils');
3 |
4 | for (let vertexId in puccini.arguments) {
5 | let mappings = puccini.arguments[vertexId];
6 | let vertex = clout.vertexes[vertexId];
7 | if (vertex === undefined)
8 | continue;
9 |
10 | if (!vertex.metadata.turandot)
11 | vertex.metadata.turandot = {};
12 | vertex.metadata.turandot.version = '1.0';
13 | vertex.metadata.turandot.resources = JSON.parse(mappings);
14 | }
15 |
16 | tosca.addHistory('kubernetes.resources.update-mappings');
17 | puccini.write(clout);
18 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/nodes.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | imports:
4 |
5 | - capabilities.yaml
6 | - relationships.yaml
7 |
8 | node_types:
9 |
10 | Service:
11 | description: >-
12 | Represents a microservice: a controlled pod (Deployment) plus a controlled endpoint (Service).
13 | capabilities:
14 | metadata: Metadata
15 | service: Service
16 | deployment: Deployment
17 | requirements:
18 | - route:
19 | capability: Service
20 | relationship: Route
21 | occurrences: [ 0, UNBOUNDED ]
22 |
--------------------------------------------------------------------------------
/scripts/publish-container-image:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | m 'Login to docker.io'
9 | buildah login docker.io
10 |
11 | function push () {
12 | local EXECUTABLE=$1
13 | local IMAGE=$EXECUTABLE
14 | local LOCAL=localhost/$IMAGE
15 | local REMOTE=docker://docker.io/tliron/$IMAGE
16 | #local REMOTE=docker://$(minikube ip):5000/tliron/$IMAGE
17 |
18 | skopeo delete --tls-verify=false "$REMOTE" || true
19 | buildah push --tls-verify=false "$LOCAL" "$REMOTE"
20 | }
21 |
22 | push turandot-operator
23 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/keypairs/admin@asterisk-vnf.pub:
--------------------------------------------------------------------------------
1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDpV4zIqJNcD905igP/Ei4nwo8+LCdIGgCYUEMBjo6JITzIhVRKhpwjvYW28DJQ4mvfRHkyyGlgy2JKbrOXLYyqoFzxE99VRi+dwI0UWvQ16fRIhOYC7X103ByYfxrppl2L275t9DxYHvbrlMfLM490EKG/dWHeyHyf5SU045S6rEZXpdJUnD5CPrFSfAxeumpcHGqTN8FrzWcfX947sAOETCHVPuTMQlCvWHMtrseGMiED6M5eFrhAovwnNrrSRsCWSEk4jyHNY06SQIavoesyemahGxIXBC7SbiFXyXo5AQ5HTwbLNMcqHha0pv5oZt8QKKddRD3xhTNh1McH2mdawC1NsFhoiuKWNCghwHmR/EHN01sMKx21mJ24+HY+GGyRbxgTywfEJz5Q00JZ3rF4Kz0X4Endr098JWeC7Q5j/vpkz2gblGV6i9vkMwBr1yY0DbBI8QaI5BM6g2Ph+IFUNrL1LVWYsUzZepJMk6YTUs6+DhxxS5D3m/fnTLczxZs= emblemparade@foucault
2 |
--------------------------------------------------------------------------------
/assets/kubernetes/hello-world.yaml:
--------------------------------------------------------------------------------
1 |
2 | apiVersion: turandot.puccini.cloud/v1alpha1
3 | kind: Service
4 |
5 | metadata:
6 | name: hello-world
7 | namespace: !!string $NAMESPACE
8 |
9 | labels:
10 | app.kubernetes.io/name: hello-world-service
11 | app.kubernetes.io/instance: hello-world-service-$NAMESPACE
12 | app.kubernetes.io/version: !!string $VERSION
13 | app.kubernetes.io/component: service
14 | app.kubernetes.io/part-of: hello-world
15 | app.kubernetes.io/managed-by: turandot
16 |
17 | spec:
18 | serviceTemplateUrl: /cache/bookinfo.csar
19 | inputs:
20 | v1: hello
21 | v2: world
22 | v3: '12'
23 |
--------------------------------------------------------------------------------
/turandot/commands/operator-logs.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | func init() {
8 | operatorCommand.AddCommand(operatorLogsCommand)
9 | operatorLogsCommand.Flags().IntVarP(&tail, "tail", "t", -1, "number of most recent lines to print (<0 means all lines)")
10 | operatorLogsCommand.Flags().BoolVarP(&follow, "follow", "f", false, "keep printing incoming logs")
11 | }
12 |
13 | var operatorLogsCommand = &cobra.Command{
14 | Use: "logs",
15 | Short: "Show the logs of the Turandot operator",
16 | Run: func(cmd *cobra.Command, args []string) {
17 | Logs("operator", "operator")
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/controller/parser/outputs.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/tliron/go-ard"
7 | cloutpkg "github.com/tliron/puccini/clout"
8 | )
9 |
10 | func GetOutputs(clout *cloutpkg.Clout) (map[string]string, bool) {
11 | if tosca, ok := clout.Properties["tosca"]; ok {
12 | if outputs, ok := ard.With(tosca).Get("outputs").NilMeansZero().StringMap(); ok {
13 | outputs_ := make(map[string]string)
14 | for name, output := range outputs {
15 | outputs_[name] = fmt.Sprintf("%v", output)
16 | }
17 | return outputs_, true
18 | } else {
19 | return nil, false
20 | }
21 | } else {
22 | return nil, false
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lab/openshift/README.md:
--------------------------------------------------------------------------------
1 | Red Hat CodeReady Containers
2 | ============================
3 |
4 | https://code-ready.github.io/crc/
5 |
6 | https://cloud.redhat.com/openshift/install/crc/installer-provisioned
7 |
8 | https://github.com/code-ready/crc
9 |
10 | To access the cluster, first set up your environment by following 'crc oc-env' instructions.
11 | Then you can access it by running 'oc login -u developer -p developer https://api.crc.testing:6443'.
12 | To login as an admin, run 'oc login -u kubeadmin -p duduw-yPT9Z-hsUpq-f3pre https://api.crc.testing:6443'.
13 | To access the cluster, first set up your environment by following 'crc oc-env' instructions.
14 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/js/artifacts/update.js:
--------------------------------------------------------------------------------
1 |
2 | const tosca = require('tosca.lib.utils');
3 |
4 | for (let vertexId in clout.vertexes) {
5 | let vertex = clout.vertexes[vertexId];
6 | if (!tosca.isNodeTemplate(vertex))
7 | continue;
8 | let nodeTemplate = vertex.properties;
9 |
10 | for (let artifactName in nodeTemplate.artifacts) {
11 | let artifact = nodeTemplate.artifacts[artifactName];
12 | if (artifact.sourcePath) {
13 | let url = puccini.arguments[artifact.sourcePath];
14 | if (url !== undefined)
15 | artifact.$artifact = url;
16 | }
17 | }
18 | }
19 |
20 | tosca.addHistory('kubernetes.artifacts.update');
21 | puccini.write(clout);
22 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/orchestration/1.0/js/states/reset.js:
--------------------------------------------------------------------------------
1 |
2 | const tosca = require('tosca.lib.utils');
3 |
4 | let serviceName = puccini.arguments.service;
5 | let mode = puccini.arguments.mode;
6 |
7 | for (let vertexId in clout.vertexes) {
8 | let vertex = clout.vertexes[vertexId];
9 | if (!tosca.isNodeTemplate(vertex))
10 | continue;
11 | if (!vertex.metadata.turandot)
12 | continue;
13 | if (!vertex.metadata.turandot.states)
14 | continue;
15 |
16 | let state = vertex.metadata.turandot.states[serviceName];
17 | if ((state !== undefined) && (state.mode === mode))
18 | delete vertex.metadata.turandot.states[serviceName];
19 | }
20 |
21 | puccini.write(clout);
22 |
--------------------------------------------------------------------------------
/scripts/build:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | git_version
9 |
10 | function build () {
11 | local TOOL=$1
12 | pushd "$ROOT/$TOOL" > /dev/null
13 | go install \
14 | -ldflags " \
15 | -X 'github.com/tliron/kutil/version.GitVersion=$VERSION' \
16 | -X 'github.com/tliron/kutil/version.GitRevision=$REVISION' \
17 | -X 'github.com/tliron/kutil/version.Timestamp=$TIMESTAMP'"
18 | popd > /dev/null
19 | m "built $GOPATH/bin/$TOOL"
20 | }
21 |
22 | build turandot-operator
23 | build turandot
24 |
25 | ln --symbolic --force "$GOPATH/bin/turandot" "$GOPATH/bin/kubectl-tosca"
26 |
--------------------------------------------------------------------------------
/client/rest.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "io"
5 |
6 | kubernetesutil "github.com/tliron/kutil/kubernetes"
7 | )
8 |
9 | func (self *Client) WriteToContainer(namespace string, podName string, containerName string, reader io.Reader, targetPath string, permissions *int64) error {
10 | return kubernetesutil.WriteToContainer(self.REST, self.Config, namespace, podName, containerName, reader, targetPath, permissions)
11 | }
12 |
13 | func (self *Client) Exec(namespace string, podName string, containerName string, stdin io.Reader, stdout io.Writer, command ...string) error {
14 | return kubernetesutil.Exec(self.REST, self.Config, namespace, podName, containerName, stdin, stdout, nil, false, command...)
15 | }
16 |
--------------------------------------------------------------------------------
/turandot/commands/common.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | contextpkg "context"
5 |
6 | "github.com/tliron/commonlog"
7 | "github.com/tliron/go-transcribe"
8 | )
9 |
10 | const toolName = "turandot"
11 |
12 | var context = contextpkg.TODO()
13 |
14 | var log = commonlog.GetLogger(toolName)
15 |
16 | var filePath string
17 | var directoryPath string
18 | var url string
19 | var component string
20 | var tail int
21 | var follow bool
22 | var all bool
23 | var site string
24 | var wait bool
25 | var registry string
26 |
27 | func Transcriber() *transcribe.Transcriber {
28 | return &transcribe.Transcriber{
29 | Strict: strict,
30 | Format: format,
31 | ForTerminal: pretty,
32 | Base64: base64,
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/artifacts.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | artifact_types:
4 |
5 | Manifest:
6 | description: >-
7 | Kubernetes resource manifest
8 | file_ext: [ yaml, yml, json ]
9 |
10 | Registry:
11 | description: >-
12 | Artifact stored in a registry
13 | properties:
14 | name:
15 | description: >-
16 | Name of artifact in registry
17 | type: string
18 | registry:
19 | description: >-
20 | Registry name
21 | type: string
22 | default: default
23 |
24 | ContainerImage:
25 | description: >-
26 | OCI container image
27 | derived_from: Registry
28 | file_ext: [ tar, tar.gz, tgz ]
29 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/asterisk/extensions-vnf.conf.template:
--------------------------------------------------------------------------------
1 | [default]
2 |
3 | exten => 911,1,Log(NOTICE, $__{CALLERID(num)} called $__{EXTEN} (bot))
4 | same => n,Answer()
5 | same => n,Playback(queue-periodic-announce)
6 | same => n,Hangup()
7 |
8 | exten => _1XX,1,Log(NOTICE, $__{CALLERID(num)} called $__{EXTEN})
9 | same => n,Dial(PJSIP/trunk/sip:$__{EXTEN}@$TRUNK_IP)
10 |
11 | exten => _X.,1,Log(NOTICE, $__{CALLERID(num)} called $__{EXTEN})
12 | same => n,Dial(PJSIP/$__{EXTEN})
13 | same => n,Answer()
14 | same => n,Playback(im-sorry)
15 | same => n,Playback(extension)
16 | same => n,SayDigits($__{EXTEN})
17 | same => n,Playback(vm-isunavail)
18 | same => n,Playback(cannot-complete-as-dialed)
19 | same => n,Hangup()
20 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/orchestration/1.0/js/states/get.js:
--------------------------------------------------------------------------------
1 |
2 | const tosca = require('tosca.lib.utils');
3 |
4 | let states = {};
5 |
6 | for (let vertexId in clout.vertexes) {
7 | let vertex = clout.vertexes[vertexId];
8 | if (!tosca.isNodeTemplate(vertex))
9 | continue;
10 | let nodeTemplate = vertex.properties;
11 |
12 | if (vertex.metadata.turandot && vertex.metadata.turandot.states)
13 | for (let serviceName in vertex.metadata.turandot.states) {
14 | let nodeState = vertex.metadata.turandot.states[serviceName];
15 | let serviceStates = states[serviceName];
16 | if (serviceStates === undefined)
17 | serviceStates = states[serviceName] = {};
18 | serviceStates[nodeTemplate.name] = nodeState;
19 | }
20 | }
21 |
22 | puccini.write(states);
23 |
--------------------------------------------------------------------------------
/examples/helm/scripts/build-csar:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | mkdir --parents "$ROOT/dist"
9 |
10 | function build () {
11 | local ARCHIVE=$1
12 | local WORK=$(mktemp --directory)
13 |
14 | pushd "$HERE/.." > /dev/null
15 | rsync --recursive --relative "${@:2}" "$WORK/"
16 | popd > /dev/null
17 |
18 | pushd "$ROOT/assets/tosca" > /dev/null
19 | rsync --recursive --relative profiles "$WORK/"
20 | popd > /dev/null
21 |
22 | puccini-csar "$ROOT/dist/$ARCHIVE.csar" "$WORK"
23 |
24 | rm --recursive --force "$WORK"
25 |
26 | m "built $ROOT/dist/$ARCHIVE.csar"
27 | }
28 |
29 | build helm \
30 | helm.yaml \
31 | artifacts/charts
32 |
--------------------------------------------------------------------------------
/assets/profile-generator/kubevirt.yaml:
--------------------------------------------------------------------------------
1 | name: KubeVirt
2 | version: 0.44.1
3 |
4 | open-api: https://raw.githubusercontent.com/kubevirt/kubevirt/v0.44.1/api/openapi-spec/swagger.json
5 | reference-url: http://kubevirt.io/api-reference/v0.44.1/definitions.html
6 | output-dir: ../tosca/profiles/kubevirt/1.0
7 |
8 | groups:
9 |
10 | default: kubevirt.io
11 | imports:
12 | k8s.io.api.core:
13 | namespace_prefix: k8s
14 | file: ../../kubernetes/1.0/profile.yaml
15 |
16 | rename:
17 |
18 | # TOSCA types
19 | object: k8s:Any
20 | number: float
21 | Time: timestamp
22 | MicroTime: timestamp
23 |
24 | override:
25 |
26 | DataVolumeTemplateSpec:
27 | entity: data
28 |
29 | SourceSpec:
30 | fields:
31 | virtualMachine: { type: k8s:Any } # hack
32 |
--------------------------------------------------------------------------------
/scripts/_trap:
--------------------------------------------------------------------------------
1 |
2 | function goodbye () {
3 | local DURATION=$(date --date=@$(( "$(date +%s)" - "$TRAP_START_TIME" )) --utc +%T)
4 | local CODE=$1
5 | cd "$TRAP_DIR"
6 | if [ "$CODE" == 0 ]; then
7 | m "$(realpath --relative-to="$ROOT" "$0") succeeded! $DURATION" "$GREEN"
8 | elif [ "$CODE" == abort ]; then
9 | m "Aborted $(realpath --relative-to="$ROOT" "$0")! $DURATION" "$RED"
10 | else
11 | m "Oh no! $(realpath --relative-to="$ROOT" "$0") failed! $DURATION" "$RED"
12 | fi
13 | }
14 |
15 | function trap_EXIT () {
16 | local ERR=$?
17 | goodbye "$ERR"
18 | exit "$ERR"
19 | }
20 |
21 | function trap_INT () {
22 | goodbye abort
23 | trap - EXIT
24 | exit 1
25 | }
26 |
27 | TRAP_DIR=$PWD
28 | TRAP_START_TIME=$(date +%s)
29 |
30 | trap trap_INT INT
31 |
32 | trap trap_EXIT EXIT
33 |
--------------------------------------------------------------------------------
/controller/parser/kubernetes-artifact.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "github.com/tliron/kutil/util"
5 | "gopkg.in/yaml.v3"
6 | )
7 |
8 | //
9 | // KubernetesArtifact
10 | //
11 |
12 | type KubernetesArtifact struct {
13 | Name string `yaml:"name"`
14 | Registry string `yaml:"registry"`
15 | SourcePath string `yaml:"sourcePath"`
16 | }
17 |
18 | //
19 | // KubernetesArtifacts
20 | //
21 |
22 | type KubernetesArtifacts []*KubernetesArtifact
23 |
24 | func DecodeKubernetesArtifacts(code string) (KubernetesArtifacts, bool) {
25 | var artifacts struct {
26 | Artifacts KubernetesArtifacts `yaml:"artifacts"`
27 | }
28 | if err := yaml.Unmarshal(util.StringToBytes(code), &artifacts); err == nil {
29 | return artifacts.Artifacts, true
30 | } else {
31 | return nil, false
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/js/artifacts/get.js:
--------------------------------------------------------------------------------
1 |
2 | const traversal = require('tosca.lib.traversal');
3 | const tosca = require('tosca.lib.utils');
4 |
5 | traversal.coerce();
6 |
7 | let artifacts = [];
8 |
9 | for (let vertexId in clout.vertexes) {
10 | let vertex = clout.vertexes[vertexId];
11 | if (!tosca.isNodeTemplate(vertex))
12 | continue;
13 | let nodeTemplate = vertex.properties;
14 |
15 | for (let artifactName in nodeTemplate.artifacts) {
16 | let artifact = nodeTemplate.artifacts[artifactName];
17 |
18 | if ('cloud.puccini.kubernetes::Registry' in artifact.types)
19 | artifacts.push({
20 | name: artifact.properties.name,
21 | registry: artifact.properties.registry,
22 | sourcePath: artifact.sourcePath
23 | });
24 | }
25 | }
26 |
27 | puccini.write({artifacts: artifacts});
28 |
--------------------------------------------------------------------------------
/examples/hello-world/scripts/build-csar:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | mkdir --parents "$ROOT/dist"
9 |
10 | function build () {
11 | local ARCHIVE=$1
12 | local WORK=$(mktemp --directory)
13 |
14 | pushd "$HERE/.." > /dev/null
15 | rsync --recursive --relative "${@:2}" "$WORK/"
16 | popd > /dev/null
17 |
18 | pushd "$ROOT/assets/tosca" > /dev/null
19 | rsync --recursive --relative profiles "$WORK/"
20 | popd > /dev/null
21 |
22 | puccini-csar "$ROOT/dist/$ARCHIVE.csar" "$WORK"
23 |
24 | rm --recursive --force "$WORK"
25 |
26 | m "built $ROOT/dist/$ARCHIVE.csar"
27 | }
28 |
29 | build hello-world \
30 | hello-world.yaml \
31 | artifacts/scriptlets \
32 | artifacts/scripts
33 |
--------------------------------------------------------------------------------
/apis/clientset/versioned/typed/turandot.puccini.cloud/v1alpha1/fake/fake_turandot.puccini.cloud_client.go:
--------------------------------------------------------------------------------
1 | // Code generated by client-gen. DO NOT EDIT.
2 |
3 | package fake
4 |
5 | import (
6 | v1alpha1 "github.com/tliron/turandot/apis/clientset/versioned/typed/turandot.puccini.cloud/v1alpha1"
7 | rest "k8s.io/client-go/rest"
8 | testing "k8s.io/client-go/testing"
9 | )
10 |
11 | type FakeTurandotV1alpha1 struct {
12 | *testing.Fake
13 | }
14 |
15 | func (c *FakeTurandotV1alpha1) Services(namespace string) v1alpha1.ServiceInterface {
16 | return &FakeServices{c, namespace}
17 | }
18 |
19 | // RESTClient returns a RESTClient that is used to communicate
20 | // with API server by this client implementation.
21 | func (c *FakeTurandotV1alpha1) RESTClient() rest.Interface {
22 | var ret *rest.RESTClient
23 | return ret
24 | }
25 |
--------------------------------------------------------------------------------
/turandot/commands/service-clout.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/tliron/kutil/terminal"
6 | "github.com/tliron/kutil/util"
7 | )
8 |
9 | func init() {
10 | serviceCommand.AddCommand(serviceCloutCommand)
11 | }
12 |
13 | var serviceCloutCommand = &cobra.Command{
14 | Use: "clout [SERVICE NAME]",
15 | Short: "Get a deployed service's Clout",
16 | Args: cobra.ExactArgs(1),
17 | Run: func(cmd *cobra.Command, args []string) {
18 | serviceName := args[0]
19 | Clout(serviceName)
20 | },
21 | }
22 |
23 | func Clout(serviceName string) {
24 | // TODO: in cluster mode we must specify the namespace
25 | namespace := ""
26 |
27 | clout, err := NewClient().Turandot().GetServiceClout(namespace, serviceName)
28 | util.FailOnError(err)
29 |
30 | terminal.Println(clout)
31 | }
32 |
--------------------------------------------------------------------------------
/examples/self-contained/scripts/build-csar:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | mkdir --parents "$ROOT/dist"
9 |
10 | function build () {
11 | local ARCHIVE=$1
12 | local WORK=$(mktemp --directory)
13 |
14 | pushd "$HERE/.." > /dev/null
15 | rsync --recursive --relative "${@:2}" "$WORK/"
16 | popd > /dev/null
17 |
18 | pushd "$ROOT/assets/tosca" > /dev/null
19 | rsync --recursive --relative profiles "$WORK/"
20 | popd > /dev/null
21 |
22 | puccini-csar "$ROOT/dist/$ARCHIVE.csar" "$WORK"
23 |
24 | rm --recursive --force "$WORK"
25 |
26 | m "built $ROOT/dist/$ARCHIVE.csar"
27 | }
28 |
29 | build self-contained \
30 | self-contained.yaml \
31 | artifacts/images \
32 | artifacts/scriptlets \
33 | artifacts/scripts
34 |
--------------------------------------------------------------------------------
/controller/parser/orchestration-state.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "github.com/tliron/kutil/util"
5 | "gopkg.in/yaml.v3"
6 | )
7 |
8 | //
9 | // OrchestrationNodeState
10 | //
11 |
12 | type OrchestrationNodeState struct {
13 | Mode string `yaml:"mode"`
14 | State string `yaml:"state"`
15 | Message string `yaml:"message"`
16 | }
17 |
18 | //
19 | // OrchestrationNodeStates
20 | //
21 |
22 | type OrchestrationNodeStates map[string]*OrchestrationNodeState
23 |
24 | //
25 | // OrchestrationStates
26 | //
27 |
28 | type OrchestrationStates map[string]OrchestrationNodeStates
29 |
30 | func DecodeOrchestrationStates(code string) (OrchestrationStates, bool) {
31 | var self OrchestrationStates
32 | if err := yaml.Unmarshal(util.StringToBytes(code), &self); err == nil {
33 | return self, true
34 | } else {
35 | return nil, false
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/mariadb/nodes.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | imports:
4 |
5 | - capabilities.yaml
6 | - namespace_prefix: k8s
7 | file: ../kubernetes/1.0/profile.yaml
8 |
9 | node_types:
10 |
11 | DB:
12 | capabilities:
13 | metadata: k8s:Metadata
14 | db: DB
15 |
16 | # Operator:
17 | # capabilities:
18 | # metadata: k8s:Metadata
19 | # deployment: k8s:Deployment
20 | # crd: k8s:CustomResourceDefinition
21 | #
22 | # OperatorStorage:
23 | # capabilities:
24 | # metadata: k8s:Metadata
25 | # pv: k8s:PersistentVolume
26 | # pvc: k8s:PersistentVolumeClaim
27 | #
28 | # OperatorServiceAccount:
29 | # capabilities:
30 | # metadata: k8s:Metadata
31 | # service-account: k8s:ServiceAccount
32 | # role: k8s:Role
33 | # role-binding: k8s:RoleBinding
34 |
--------------------------------------------------------------------------------
/lab/openshift/test:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | # oc login -u kubeadmin ...
9 |
10 | oc new-project workspace || true
11 | turandot operator uninstall --wait -v
12 | turandot repository uninstall --wait -v
13 | kubectl delete events --all
14 |
15 | if [ "$1" == -b ]; then
16 | "$ROOT/scripts/build-container-image"
17 | "$ROOT/scripts/publish-container-image"
18 | # Reminder: clean ~/.local/share/containers/ occassionally!
19 | fi
20 |
21 | turandot operator install --site=central --wait -v
22 | turandot repository create default --provider=openshift --wait -v
23 | turandot service deploy hello-world --file=dist/hello-world.csar -v
24 | turandot service deploy helm-hello-world --file=dist/helm-hello-world.csar -v
25 | turandot operator logs --follow
26 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/profile.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | metadata:
4 |
5 | puccini.scriptlet.import:kubernetes.executions: js/executions.js
6 | puccini.scriptlet.import:kubernetes.artifacts.get: js/artifacts/get.js
7 | puccini.scriptlet.import:kubernetes.artifacts.update: js/artifacts/update.js
8 | puccini.scriptlet.import:kubernetes.resources.get: js/resources/get.js
9 | puccini.scriptlet.import:kubernetes.resources.get-mappings: js/resources/get-mappings.js
10 | puccini.scriptlet.import:kubernetes.resources.update-attributes: js/resources/update-attributes.js
11 | puccini.scriptlet.import:kubernetes.resources.update-mappings: js/resources/update-mappings.js
12 |
13 | namespace: cloud.puccini.kubernetes
14 |
15 | imports:
16 |
17 | - artifacts.yaml
18 | - groups.yaml
19 | - policies.yaml
20 | - interfaces.yaml
21 | - repositories.yaml
22 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/cloud-config/asterisk-vnf.yaml:
--------------------------------------------------------------------------------
1 | #cloud-config
2 |
3 | password: fedora
4 | chpasswd:
5 | expire: false
6 |
7 | users:
8 | - default
9 | - name: admin
10 | groups: wheel
11 | shell: /bin/bash
12 | sudo: ALL=(ALL) NOPASSWD:ALL
13 | ssh-authorized-keys:
14 | - "$KEY"
15 |
16 | #lock_passwd: false
17 | # TODO: doesn't work!
18 | #passwd: $1$VWxq3zKg$fY3uElTYsu16yh7ICDKdT1
19 | # To generate password hash:
20 | # echo mypassword | mkpasswd --stdin --method=SHA-512 --rounds=4096
21 | # or:
22 | # echo mypassword | openssl passwd -stdin -1
23 |
24 | runcmd:
25 | - systemctl enable asterisk.service
26 | - systemctl start asterisk.service
27 | # We don't need cloud-init anymore:
28 | - systemctl disable cloud-config.service
29 | - systemctl disable cloud-final.service
30 | - systemctl disable cloud-init-local.service
31 | - systemctl disable cloud-init.service
32 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/orchestration/1.0/js/states/set.js:
--------------------------------------------------------------------------------
1 |
2 | const tosca = require('tosca.lib.utils');
3 |
4 | let serviceName = puccini.arguments.service;
5 | let nodeTemplateName = puccini.arguments.nodeTemplate;
6 |
7 | let mode = puccini.arguments.mode;
8 | let state = puccini.arguments.state;
9 | let message = puccini.arguments.message;
10 |
11 | for (let vertexId in clout.vertexes) {
12 | let vertex = clout.vertexes[vertexId];
13 | if (!tosca.isNodeTemplate(vertex))
14 | continue;
15 | let nodeTemplate = vertex.properties;
16 | if (nodeTemplate.name !== nodeTemplateName)
17 | continue;
18 |
19 | if (!vertex.metadata.turandot)
20 | vertex.metadata.turandot = {};
21 | if (!vertex.metadata.turandot.states)
22 | vertex.metadata.turandot.states = {}
23 |
24 | vertex.metadata.turandot.states[serviceName] = {
25 | mode: mode,
26 | state: state,
27 | message: message
28 | };
29 |
30 | puccini.write(clout);
31 | break;
32 | }
33 |
--------------------------------------------------------------------------------
/turandot/commands/shell.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 |
8 | "github.com/tliron/kutil/kubernetes"
9 | "github.com/tliron/kutil/util"
10 | "github.com/tliron/turandot/controller"
11 | )
12 |
13 | func Shell(appNameSuffix string, containerName string) {
14 | util.ToRawTerminal(func() error {
15 | return NewClient().Shell(appNameSuffix, containerName, os.Stdin, os.Stdout, os.Stderr)
16 | })
17 | }
18 |
19 | func (self *Client) Shell(appNameSuffix string, containerName string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
20 | appName := fmt.Sprintf("%s-%s", controller.NamePrefix, appNameSuffix)
21 |
22 | if podName, err := kubernetes.GetFirstPodName(self.Context, self.Kubernetes, self.Namespace, appName); err == nil {
23 | return kubernetes.Exec(self.REST, self.Config, self.Namespace, podName, containerName, stdin, stdout, stderr, true, "sh")
24 | } else {
25 | return err
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/helm/helm.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | metadata:
4 |
5 | template_name: Helm Example
6 | template_author: Turandot
7 |
8 | imports:
9 |
10 | - namespace_prefix: helm
11 | file: profiles/helm/1.0/profile.yaml
12 |
13 | topology_template:
14 |
15 | inputs:
16 |
17 | namespace:
18 | type: string
19 | default: workspace
20 |
21 | node_templates:
22 |
23 | hello-world:
24 | type: helm:Release
25 | properties:
26 | # If the "name" property is not specified the node template name will be used
27 | chart: { get_artifact: [ SELF, chart ] }
28 | namespace: { get_input: namespace }
29 | values:
30 | nameOverride: helm # is used as both a postfix and a label
31 | service.type: LoadBalancer
32 | service.port: '8080'
33 | artifacts:
34 | chart:
35 | type: helm:Chart
36 | file: artifacts/charts/hello-world.tar.gz
37 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/orchestration/1.0/data.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | data_types:
4 |
5 | ProvisioningProfile:
6 | description: >-
7 | All provisioning profiles must have a name unique to the orchestration platform.
8 |
9 | The name can refer to a subtituted service template, which can be included in the CSAR or
10 | in an annotated inventory.
11 |
12 | Variables can be used to further specify variations for the named profile, e.g. the profile
13 | version, optional features, etc.
14 | properties:
15 | name:
16 | type: string
17 | variables:
18 | type: map
19 | entry_schema: string
20 | required: false
21 |
22 | # Execution
23 |
24 | ExecutionRequirements:
25 | description: >-
26 | Base type for execution requirements.
27 |
28 | # Artifacts
29 |
30 | FilePermissions:
31 | derived_from: integer
32 | constraints:
33 | - in_range: [ 0, 0777 ]
34 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/orchestration/1.0/js/policies.js:
--------------------------------------------------------------------------------
1 |
2 | const traversal = require('tosca.lib.traversal');
3 | const tosca = require('tosca.lib.utils');
4 |
5 | traversal.coerce();
6 |
7 | let policies = {};
8 |
9 | for (let vertexId in clout.vertexes) {
10 | let vertex = clout.vertexes[vertexId];
11 | if (!tosca.isTosca(vertex, 'Policy'))
12 | continue;
13 | let policy = vertex.properties;
14 |
15 | if ('cloud.puccini.turandot.orchestration::Provisioning' in policy.types)
16 | generatePolicy(policy, tosca.getPolicyTargets(vertex), 'provisioning');
17 | }
18 |
19 | puccini.write(policies);
20 |
21 | function generatePolicy(policy, targets, type) {
22 | for (let t = 0, l = targets.length; t < l; t++) {
23 | let target = targets[t];
24 | let targetPolicies = policies[target.name];
25 | if (targetPolicies === undefined)
26 | targetPolicies = policies[target.name] = [];
27 | targetPolicies.push({
28 | type: type,
29 | properties: policy.properties,
30 | });
31 | }
32 | }
--------------------------------------------------------------------------------
/apis/informers/externalversions/internalinterfaces/factory_interfaces.go:
--------------------------------------------------------------------------------
1 | // Code generated by informer-gen. DO NOT EDIT.
2 |
3 | package internalinterfaces
4 |
5 | import (
6 | time "time"
7 |
8 | versioned "github.com/tliron/turandot/apis/clientset/versioned"
9 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10 | runtime "k8s.io/apimachinery/pkg/runtime"
11 | cache "k8s.io/client-go/tools/cache"
12 | )
13 |
14 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer.
15 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer
16 |
17 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle
18 | type SharedInformerFactory interface {
19 | Start(stopCh <-chan struct{})
20 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer
21 | }
22 |
23 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions.
24 | type TweakListOptionsFunc func(*v1.ListOptions)
25 |
--------------------------------------------------------------------------------
/scripts/generate-apis:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | go get -d k8s.io/code-generator@v$K8S_VERSION
9 | CODE_GENERATOR=$(go list -f '{{.Dir}}' -m k8s.io/code-generator@v$K8S_VERSION)
10 |
11 | chmod +x "$CODE_GENERATOR/generate-groups.sh"
12 |
13 | WORK=$(mktemp --directory)
14 |
15 | m "work directory: $WORK"
16 |
17 | copy_function goodbye old_goodbye
18 | function goodbye () {
19 | m "deleting work directory: $WORK"
20 | rm --recursive "$WORK"
21 | old_goodbye $1
22 | }
23 |
24 | GOPATH=$GOPATH \
25 | "$CODE_GENERATOR/generate-groups.sh" \
26 | all \
27 | github.com/tliron/turandot/apis \
28 | github.com/tliron/turandot/resources \
29 | turandot.puccini.cloud:v1alpha1 \
30 | --go-header-file "$HERE/header.go.txt" \
31 | --output-base "$WORK"
32 |
33 | if [ "$1" == -c ]; then
34 | rm --recursive --force "$ROOT/apis"
35 | fi
36 |
37 | cp --recursive \
38 | "$WORK/github.com/tliron/turandot/"* \
39 | "$ROOT/"
40 |
--------------------------------------------------------------------------------
/scripts/deploy:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | for ARG in "$@"; do
9 | case "$ARG" in
10 | -c)
11 | NAMESPACE=$WORKSPACE "$HERE/delete"
12 | ;;
13 | --cluster)
14 | CLUSTER_MODE=true
15 | ;;
16 | esac
17 | done
18 |
19 | export VERSION=1.0
20 | export REGISTRY_URL=$(kubectl_registry_url)
21 |
22 | kubectl create namespace "$WORKSPACE" || true
23 |
24 | m "deploying operator to namespace \"$WORKSPACE\"..."
25 | kubectl_apply_template "$ROOT/assets/kubernetes/custom-resource-definition.yaml"
26 | kubectl_apply_template "$ROOT/assets/namespace.yaml"
27 | if [ "$CLUSTER_MODE" == true ]; then
28 | kubectl_apply_template "$ROOT/assets/kubernetes/cluster-mode-authorization.yaml"
29 | fi
30 | kubectl_apply_template "$ROOT/assets/kubernetes/turandot.yaml"
31 | kubectl_apply_template "$ROOT/assets/kubernetes/hello-world.yaml"
32 |
33 | m "waiting for operator to start..."
34 | kubectl_wait_for_deployment turandot-operator
35 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/scripts/build-asterisk-cnf-container-image:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | BASE_IMAGE=fedora:33
9 | IMAGE=asterisk-cnf
10 | LOCAL=localhost/$IMAGE
11 | IMAGES=$(readlink --canonicalize "$HERE/../artifacts/images")
12 |
13 | CONTAINER_ID=$(buildah from "$BASE_IMAGE")
14 |
15 | function r () {
16 | buildah run "$CONTAINER_ID" -- "$@"
17 | }
18 |
19 | m 'installing asterisk packages...'
20 | r dnf --assumeyes install asterisk asterisk-pjsip asterisk-sounds-core-en asterisk-sounds-core-en-ulaw gettext iproute
21 | r dnf --assumeyes clean all
22 |
23 | buildah config \
24 | --entrypoint '/usr/sbin/asterisk -f -U asterisk -G asterisk -vvvg' \
25 | --author Turandot \
26 | --created-by buildah \
27 | "$CONTAINER_ID"
28 |
29 | buildah commit "$CONTAINER_ID" "$LOCAL"
30 |
31 | mkdir --parents "$IMAGES"
32 |
33 | "$ROOT/scripts/save-portable-container-image" "$LOCAL" "$IMAGES/$IMAGE.tar.gz"
34 |
--------------------------------------------------------------------------------
/lab/minikube/test:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | minikube profile central
9 | kubectl config set-context --current --namespace=workspace
10 |
11 | turandot operator uninstall --wait -v
12 | reposure operator uninstall --wait -v
13 |
14 | kubectl delete events --all
15 |
16 | if [ "$1" == -b ]; then
17 | "$ROOT/scripts/build-container-image"
18 | "$ROOT/scripts/publish-container-image"
19 | # Reminder: clean ~/.local/share/containers/ occassionally!
20 | fi
21 |
22 | reposure operator install --role=view --wait -v
23 | reposure registry create default --provider=minikube --wait -v
24 | reposure image delete default --all -v
25 |
26 | turandot operator install --site=central --role=view --wait -v
27 | turandot service deploy hello-world --file=dist/hello-world.csar -v
28 | turandot service deploy self-contained --file=dist/self-contained.csar -v
29 | turandot service deploy helm --file=dist/helm.csar -v
30 | turandot operator logs --follow
31 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/templates/hpa.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.autoscaling.enabled }}
2 | apiVersion: autoscaling/v2beta1
3 | kind: HorizontalPodAutoscaler
4 | metadata:
5 | name: {{ include "hello-world.fullname" . }}
6 | labels:
7 | {{- include "hello-world.labels" . | nindent 4 }}
8 | spec:
9 | scaleTargetRef:
10 | apiVersion: apps/v1
11 | kind: Deployment
12 | name: {{ include "hello-world.fullname" . }}
13 | minReplicas: {{ .Values.autoscaling.minReplicas }}
14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }}
15 | metrics:
16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
17 | - type: Resource
18 | resource:
19 | name: cpu
20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
21 | {{- end }}
22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
23 | - type: Resource
24 | resource:
25 | name: memory
26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
27 | {{- end }}
28 | {{- end }}
29 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/orchestration/1.0/artifacts.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | imports:
4 |
5 | - data.yaml
6 |
7 | artifact_types:
8 |
9 | Key:
10 | properties:
11 | generate:
12 | type: boolean
13 | default: false
14 |
15 | Deployable:
16 | description: >-
17 | A file intended to be deployed to a container or machine.
18 |
19 | The artifact's "deploy_path" *must* be set.
20 | properties:
21 | permissions:
22 | description: >-
23 | The permissions to apply to the file after deploying it, e.g. via `chmod`. If not
24 | specified then default permissions will be used.
25 |
26 | These permissions will also apply to created directories (in addition to the "executable"
27 | flag).
28 |
29 | Note that literal octals in YAML can be specified beginning with a '0', e.g. 0500.
30 | type: FilePermissions
31 | required: false
32 |
33 | Executable:
34 | derived_from: Deployable
35 | properties:
36 | permissions:
37 | default: 0755
38 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 |
2 | builds:
3 |
4 | - id: turandot
5 | main: ./turandot
6 | binary: turandot
7 | goarch:
8 | - amd64
9 | goos:
10 | - linux
11 | - darwin
12 | - windows
13 | ldflags:
14 | - -X 'github.com/tliron/kutil/version.GitVersion={{.Env.VERSION}}'
15 | - -X 'github.com/tliron/kutil/version.GitRevision={{.Env.REVISION}}'
16 | - -X 'github.com/tliron/kutil/version.Timestamp={{.Env.TIMESTAMP}}'
17 |
18 | nfpms:
19 |
20 | - formats:
21 | - rpm
22 | - deb
23 | homepage: https://github.com/tliron/turandot
24 | maintainer: Tal Liron
25 | description: Compose and orchestrate Kubernetes workloads using TOSCA.
26 | license: Apache 2.0
27 |
28 | archives:
29 |
30 | - files:
31 | - README.md
32 | - LICENSE
33 | - NOTICE
34 | - assets/kubernetes/**/*
35 | - assets/tosca/**/*
36 | - examples/**/*
37 |
38 | format_overrides:
39 | - goos: windows
40 | format: zip
41 |
42 | checksum:
43 |
44 | name_template: checksums.txt
45 |
46 | release:
47 |
48 | #disable: true
49 | #prerelease: true
50 |
51 | github:
52 | owner: tliron
53 | name: turandot
54 |
--------------------------------------------------------------------------------
/controller/tosca.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | contextpkg "context"
5 |
6 | "github.com/tliron/exturl"
7 | "github.com/tliron/go-ard"
8 | "github.com/tliron/go-transcribe"
9 | "github.com/tliron/kutil/util"
10 | )
11 |
12 | func (self *Controller) CompileServiceTemplate(context contextpkg.Context, serviceTemplateURL string, inputs map[string]string, cloutPath string, urlContext *exturl.Context) (string, error) {
13 | self.Log.Infof("compiling TOSCA service template: %s", serviceTemplateURL)
14 | self.Log.Infof("inputs: %s", inputs)
15 |
16 | // Decode inputs
17 | inputs_ := make(map[string]ard.Value)
18 | for key, input := range inputs {
19 | var err error
20 | if inputs_[key], _, err = ard.DecodeYAML(util.StringToBytes(input), false); err != nil {
21 | return "", err
22 | }
23 | }
24 |
25 | if file, err := transcribe.OpenFileForWrite(cloutPath); err == nil {
26 | defer file.Close()
27 | if err := CompileTOSCA(context, serviceTemplateURL, inputs_, file, urlContext); err == nil {
28 | return util.GetFileHash(cloutPath)
29 | } else {
30 | return "", err
31 | }
32 | } else {
33 | return "", err
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/apis/informers/externalversions/turandot.puccini.cloud/v1alpha1/interface.go:
--------------------------------------------------------------------------------
1 | // Code generated by informer-gen. DO NOT EDIT.
2 |
3 | package v1alpha1
4 |
5 | import (
6 | internalinterfaces "github.com/tliron/turandot/apis/informers/externalversions/internalinterfaces"
7 | )
8 |
9 | // Interface provides access to all the informers in this group version.
10 | type Interface interface {
11 | // Services returns a ServiceInformer.
12 | Services() ServiceInformer
13 | }
14 |
15 | type version struct {
16 | factory internalinterfaces.SharedInformerFactory
17 | namespace string
18 | tweakListOptions internalinterfaces.TweakListOptionsFunc
19 | }
20 |
21 | // New returns a new Interface.
22 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
23 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
24 | }
25 |
26 | // Services returns a ServiceInformer.
27 | func (v *version) Services() ServiceInformer {
28 | return &serviceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
29 | }
30 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/profiles/telephony/profile.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | namespace: cloud.puccini.telephony
4 |
5 | imports:
6 |
7 | - namespace_prefix: ns
8 | file: ../network-service/profile.yaml
9 |
10 | node_types:
11 |
12 | PBX:
13 | derived_from: ns:NetworkFunction
14 | properties:
15 | endpoints:
16 | type: map
17 | entry_schema: Endpoint
18 | required: false
19 | capabilities:
20 | trunks: Trunks
21 | requirements:
22 | - trunk:
23 | capability: Trunks
24 | relationship: Trunk
25 |
26 | capability_types:
27 |
28 | Trunks:
29 | derived_from: ns:Connectable
30 | properties:
31 | endpoints:
32 | type: map
33 | entry_schema: Endpoint
34 | required: false
35 |
36 | relationship_types:
37 |
38 | Trunk:
39 | properties:
40 | endpoint:
41 | type: string
42 | attributes:
43 | ip:
44 | type: string
45 |
46 | data_types:
47 |
48 | Endpoint:
49 | properties:
50 | username:
51 | type: string
52 | password:
53 | type: string
54 |
--------------------------------------------------------------------------------
/apis/applyconfiguration/internal/internal.go:
--------------------------------------------------------------------------------
1 | // Code generated by applyconfiguration-gen. DO NOT EDIT.
2 |
3 | package internal
4 |
5 | import (
6 | "fmt"
7 | "sync"
8 |
9 | typed "sigs.k8s.io/structured-merge-diff/v4/typed"
10 | )
11 |
12 | func Parser() *typed.Parser {
13 | parserOnce.Do(func() {
14 | var err error
15 | parser, err = typed.NewParser(schemaYAML)
16 | if err != nil {
17 | panic(fmt.Sprintf("Failed to parse schema: %v", err))
18 | }
19 | })
20 | return parser
21 | }
22 |
23 | var parserOnce sync.Once
24 | var parser *typed.Parser
25 | var schemaYAML = typed.YAMLObject(`types:
26 | - name: __untyped_atomic_
27 | scalar: untyped
28 | list:
29 | elementType:
30 | namedType: __untyped_atomic_
31 | elementRelationship: atomic
32 | map:
33 | elementType:
34 | namedType: __untyped_atomic_
35 | elementRelationship: atomic
36 | - name: __untyped_deduced_
37 | scalar: untyped
38 | list:
39 | elementType:
40 | namedType: __untyped_atomic_
41 | elementRelationship: atomic
42 | map:
43 | elementType:
44 | namedType: __untyped_deduced_
45 | elementRelationship: separable
46 | `)
47 |
--------------------------------------------------------------------------------
/turandot/commands/operator-install.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/tliron/kutil/util"
6 | )
7 |
8 | var clusterRole string
9 | var sourceRegistry string
10 |
11 | func init() {
12 | operatorCommand.AddCommand(operatorInstallCommand)
13 | operatorInstallCommand.Flags().StringVarP(&site, "site", "s", "default", "site name")
14 | operatorInstallCommand.Flags().BoolVarP(&clusterMode, "cluster", "c", false, "cluster mode")
15 | operatorInstallCommand.Flags().StringVarP(&clusterRole, "role", "e", "cluster-admin", "cluster role")
16 | operatorInstallCommand.Flags().StringVarP(&sourceRegistry, "registry", "g", "docker.io", "source registry host (use special value \"internal\" to discover internally deployed registry)")
17 | operatorInstallCommand.Flags().BoolVarP(&wait, "wait", "w", false, "wait for installation to succeed")
18 | }
19 |
20 | var operatorInstallCommand = &cobra.Command{
21 | Use: "install",
22 | Short: "Install the Turandot operator",
23 | Run: func(cmd *cobra.Command, args []string) {
24 | err := NewClient().Turandot().InstallOperator(site, sourceRegistry, wait)
25 | util.FailOnError(err)
26 | },
27 | }
28 |
--------------------------------------------------------------------------------
/turandot/commands/template-pull.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/tliron/kutil/util"
8 | )
9 |
10 | func init() {
11 | templateCommand.AddCommand(templatePullCommand)
12 | templatePullCommand.Flags().StringVarP(®istry, "registry", "r", "default", "name of registry")
13 | }
14 |
15 | var templatePullCommand = &cobra.Command{
16 | Use: "pull [SERVICE TEMPLATE NAME]",
17 | Short: "Pull a service template as a CSAR from a registry to stdout",
18 | Args: cobra.ExactArgs(1),
19 | Run: func(cmd *cobra.Command, args []string) {
20 | serviceTemplateName := args[0]
21 | PullServiceTemplate(serviceTemplateName)
22 | },
23 | }
24 |
25 | func PullServiceTemplate(serviceTemplateName string) {
26 | turandot := NewClient().Turandot()
27 | registry_, err := turandot.Reposure.RegistryClient().Get(namespace, registry)
28 | util.FailOnError(err)
29 | command, err := turandot.Reposure.SurrogateCommandClient(registry_)
30 | util.FailOnError(err)
31 |
32 | imageName := turandot.RegistryImageNameForServiceTemplateName(serviceTemplateName)
33 | err = command.PullLayer(imageName, os.Stdout)
34 | util.FailOnError(err)
35 | }
36 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/scripts/asterisk/configure-cnf.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | cd /tmp
5 |
6 | if [ -f configured ]; then
7 | exit 0
8 | fi
9 |
10 | function ip_address () {
11 | # See: https://stackoverflow.com/a/26694162
12 | ip -family inet address show $1 | grep --only-matching --perl-regexp '(?<=inet\s)\d+(\.\d+){3}'
13 | }
14 |
15 | EXTERNAL_IP=$1
16 |
17 | if [ "$EXTERNAL_IP" == '' ]; then
18 | exit 1
19 | fi
20 |
21 | CONTROL_PLANE_IP=$(ip_address eth0)
22 | DATA_PLANE_IP=$(ip_address net1)
23 | TRUNK_IP=192.68.1.1 # CNF
24 |
25 | cp /dev/stdin clout.yaml
26 |
27 | cat extensions.conf.template | __=\\$ TRUNK_IP=$TRUNK_IP envsubst > extensions.conf
28 | cat pjsip.conf.template | CONTROL_PLANE_IP=$CONTROL_PLANE_IP DATA_PLANE_IP=$DATA_PLANE_IP EXTERNAL_IP=$EXTERNAL_IP TRUNK_IP=$TRUNK_IP envsubst > pjsip.conf
29 |
30 | mv pjsip.conf /etc/asterisk/pjsip.conf
31 | mv rtp.conf /etc/asterisk/rtp.conf
32 | mv extensions.conf /etc/asterisk/extensions.conf
33 |
34 | chown asterisk:asterisk \
35 | /etc/asterisk/pjsip.conf \
36 | /etc/asterisk/rtp.conf \
37 | /etc/asterisk/extensions.conf
38 |
39 | sudo asterisk -rx 'core restart now'
40 |
41 | touch configured
42 |
--------------------------------------------------------------------------------
/scripts/build-container-image:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | "$HERE/build"
9 |
10 | # https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/building_running_and_managing_containers/using_red_hat_universal_base_images_standard_minimal_and_runtimes
11 |
12 | function build () {
13 | local EXECUTABLE=$1
14 | local IMAGE=$EXECUTABLE
15 | local LOCAL=localhost/$IMAGE
16 | local BASE_IMAGE=registry.access.redhat.com/ubi8/ubi
17 | # registry.redhat.io/ubi8/ubi
18 | # note: ubi-minimal does not have "tar" which is needed for kubectl cp
19 |
20 | local CONTAINER_ID=$(buildah from "$BASE_IMAGE")
21 | #buildah run "$CONTAINER_ID" -- dnf --assumeyes install ansible
22 | #buildah run "$CONTAINER_ID" -- dnf --assumeyes clean all
23 | buildah copy "$CONTAINER_ID" "$GOPATH/bin/$EXECUTABLE" /usr/bin/
24 | buildah copy "$CONTAINER_ID" "$(which helm)" /usr/bin/
25 | buildah config \
26 | --entrypoint "/usr/bin/$EXECUTABLE" \
27 | --author Turandot \
28 | --created-by buildah \
29 | "$CONTAINER_ID"
30 | buildah commit "$CONTAINER_ID" "$LOCAL"
31 | }
32 |
33 | build turandot-operator
34 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | name: hello-world
3 | description: A Helm chart for Kubernetes
4 |
5 | # A chart can be either an 'application' or a 'library' chart.
6 | #
7 | # Application charts are a collection of templates that can be packaged into versioned archives
8 | # to be deployed.
9 | #
10 | # Library charts provide useful utilities or functions for the chart developer. They're included as
11 | # a dependency of application charts to inject those utilities and functions into the rendering
12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed.
13 | type: application
14 |
15 | # This is the chart version. This version number should be incremented each time you make changes
16 | # to the chart and its templates, including the app version.
17 | # Versions are expected to follow Semantic Versioning (https://semver.org/)
18 | version: 0.1.0
19 |
20 | # This is the version number of the application being deployed. This version number should be
21 | # incremented each time you make changes to the application. Versions are not expected to
22 | # follow Semantic Versioning. They should reflect the version the application is using.
23 | appVersion: 1.16.0
24 |
--------------------------------------------------------------------------------
/turandot/commands/service-output.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/tliron/kutil/terminal"
6 | "github.com/tliron/kutil/util"
7 | )
8 |
9 | func init() {
10 | serviceCommand.AddCommand(serviceOutputCommand)
11 | }
12 |
13 | var serviceOutputCommand = &cobra.Command{
14 | Use: "output [SERVICE NAME] [OUTPUT NAME]",
15 | Short: "Get a deployed service's output",
16 | Args: cobra.ExactArgs(2),
17 | Run: func(cmd *cobra.Command, args []string) {
18 | serviceName := args[0]
19 | outputName := args[1]
20 | ServiceOutput(serviceName, outputName)
21 | },
22 | }
23 |
24 | func ServiceOutput(serviceName string, outputName string) {
25 | // TODO: in cluster mode we must specify the namespace
26 | namespace := ""
27 |
28 | service, err := NewClient().Turandot().GetService(namespace, serviceName)
29 | util.FailOnError(err)
30 |
31 | if service.Status.Outputs != nil {
32 | if output, ok := service.Status.Outputs[outputName]; ok {
33 | // TODO: unpack the YAML
34 | // TODO: support output in various formats
35 | terminal.Println(output)
36 | return
37 | }
38 | }
39 |
40 | util.Failf("output %q not found in service %q", outputName, serviceName)
41 | }
42 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/mariadb/capabilities.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | imports:
4 |
5 | - namespace_prefix: k8s
6 | file: ../kubernetes/1.0/profile.yaml
7 |
8 | capability_types:
9 |
10 | Connectable: {}
11 |
12 | # See: https://github.com/abalki001/mariadb-operator/tree/master/deploy
13 | DB:
14 | metadata:
15 | turandot.apiVersion: mariadb.persistentsys/v1alpha1
16 | turandot.kind: MariaDB
17 | derived_from: Connectable
18 | properties:
19 | database:
20 | description: >-
21 | New database name.
22 | type: string
23 | username:
24 | type: string
25 | password:
26 | type: string
27 | rootpwd:
28 | description: >-
29 | Root user password.
30 | type: string
31 | image:
32 | description: >-
33 | Image URL.
34 | type: string
35 | default: mariadb/server:10.3
36 | size:
37 | description: >-
38 | Size of the deployment.
39 | type: integer
40 | default: 1
41 | attributes:
42 | nodes:
43 | description: >-
44 | The names of the pods.
45 | type: list
46 | entry_schema: string
47 |
--------------------------------------------------------------------------------
/apis/informers/externalversions/turandot.puccini.cloud/interface.go:
--------------------------------------------------------------------------------
1 | // Code generated by informer-gen. DO NOT EDIT.
2 |
3 | package turandot
4 |
5 | import (
6 | internalinterfaces "github.com/tliron/turandot/apis/informers/externalversions/internalinterfaces"
7 | v1alpha1 "github.com/tliron/turandot/apis/informers/externalversions/turandot.puccini.cloud/v1alpha1"
8 | )
9 |
10 | // Interface provides access to each of this group's versions.
11 | type Interface interface {
12 | // V1alpha1 provides access to shared informers for resources in V1alpha1.
13 | V1alpha1() v1alpha1.Interface
14 | }
15 |
16 | type group struct {
17 | factory internalinterfaces.SharedInformerFactory
18 | namespace string
19 | tweakListOptions internalinterfaces.TweakListOptionsFunc
20 | }
21 |
22 | // New returns a new Interface.
23 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
24 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
25 | }
26 |
27 | // V1alpha1 returns a new v1alpha1.Interface.
28 | func (g *group) V1alpha1() v1alpha1.Interface {
29 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
30 | }
31 |
--------------------------------------------------------------------------------
/scripts/release:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 |
7 | "$HERE/build"
8 |
9 | #curl --silent --fail --location https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | \
10 | #sh -s -- -b "$GOPATH/bin"
11 | go install github.com/goreleaser/goreleaser@latest
12 |
13 | # Uses the current tag for the release version
14 | # (So you likely want to tag before running this)
15 |
16 | WORK=$(mktemp --directory)
17 |
18 | function the_end () {
19 | local ERR=$?
20 | rm --recursive --force "$WORK"
21 | exit $ERR
22 | }
23 |
24 | trap the_end EXIT
25 |
26 | # Create a clean copy of our repo
27 | rsync --recursive "$ROOT/" "$WORK"
28 | cd "$WORK"
29 | git clean -xdf
30 | find . -type f -name .gitignore -exec rm {} \;
31 |
32 | git_version
33 |
34 | ARGS=
35 | if [ "$1" == -t ]; then
36 | ARGS='--snapshot --skip-publish'
37 | fi
38 |
39 | VERSION=$VERSION REVISION=$REVISION TIMESTAMP=$TIMESTAMP \
40 | "$GOPATH/bin/goreleaser" \
41 | --parallelism=$(nproc) \
42 | --skip-validate $ARGS
43 |
44 | # Copy releases back here
45 | rm --recursive --force "$ROOT/dist/release"
46 | mkdir --parents "$ROOT/dist"
47 | rsync --recursive "$WORK/dist/" "$ROOT/dist/release"
48 |
--------------------------------------------------------------------------------
/turandot/commands/service-mode.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/tliron/kutil/terminal"
6 | "github.com/tliron/kutil/util"
7 | )
8 |
9 | func init() {
10 | serviceCommand.AddCommand(serviceModeCommand)
11 | }
12 |
13 | var serviceModeCommand = &cobra.Command{
14 | Use: "mode [SERVICE NAME] [[MODE]]",
15 | Short: "Get or set a deployed service's mode",
16 | Args: cobra.RangeArgs(1, 2),
17 | Run: func(cmd *cobra.Command, args []string) {
18 | if len(args) == 2 {
19 | SetMode(args[0], args[1])
20 | } else {
21 | GetMode(args[0])
22 | }
23 | },
24 | }
25 |
26 | func GetMode(serviceName string) {
27 | // TODO: in cluster mode we must specify the namespace
28 | namespace := ""
29 |
30 | service, err := NewClient().Turandot().GetService(namespace, serviceName)
31 | util.FailOnError(err)
32 | terminal.Println(service.Status.Mode)
33 | }
34 |
35 | func SetMode(serviceName string, mode string) {
36 | // TODO: in cluster mode we must specify the namespace
37 | namespace := ""
38 |
39 | turandot := NewClient().Turandot()
40 | service, err := turandot.GetService(namespace, serviceName)
41 | util.FailOnError(err)
42 | _, err = turandot.UpdateServiceMode(service, mode)
43 | util.FailOnError(err)
44 | }
45 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/templates/ingress.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.ingress.enabled -}}
2 | {{- $fullName := include "hello-world.fullname" . -}}
3 | {{- $svcPort := .Values.service.port -}}
4 | {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
5 | apiVersion: networking.k8s.io/v1beta1
6 | {{- else -}}
7 | apiVersion: extensions/v1beta1
8 | {{- end }}
9 | kind: Ingress
10 | metadata:
11 | name: {{ $fullName }}
12 | labels:
13 | {{- include "hello-world.labels" . | nindent 4 }}
14 | {{- with .Values.ingress.annotations }}
15 | annotations:
16 | {{- toYaml . | nindent 4 }}
17 | {{- end }}
18 | spec:
19 | {{- if .Values.ingress.tls }}
20 | tls:
21 | {{- range .Values.ingress.tls }}
22 | - hosts:
23 | {{- range .hosts }}
24 | - {{ . | quote }}
25 | {{- end }}
26 | secretName: {{ .secretName }}
27 | {{- end }}
28 | {{- end }}
29 | rules:
30 | {{- range .Values.ingress.hosts }}
31 | - host: {{ .host | quote }}
32 | http:
33 | paths:
34 | {{- range .paths }}
35 | - path: {{ . }}
36 | backend:
37 | serviceName: {{ $fullName }}
38 | servicePort: {{ $svcPort }}
39 | {{- end }}
40 | {{- end }}
41 | {{- end }}
42 |
--------------------------------------------------------------------------------
/turandot/commands/delegate-delete.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/tliron/kutil/util"
6 | )
7 |
8 | func init() {
9 | delegateCommand.AddCommand(delegateDeleteCommand)
10 | delegateDeleteCommand.Flags().BoolVarP(&all, "all", "a", false, "delete all delegates")
11 | }
12 |
13 | var delegateDeleteCommand = &cobra.Command{
14 | Use: "delete [[DELEGATE NAME]]",
15 | Short: "Delete a delegate (or all delegates)",
16 | Args: cobra.RangeArgs(0, 1),
17 | Run: func(cmd *cobra.Command, args []string) {
18 | if len(args) == 1 {
19 | delegateName := args[0]
20 | DeleteDelegate(delegateName)
21 | } else if all {
22 | DeleteAllDelegates()
23 | } else {
24 | util.Fail("must provide delegate name or specify \"--all\"")
25 | }
26 | },
27 | }
28 |
29 | func DeleteDelegate(delegateName string) {
30 | err := NewClient().Turandot().DeleteDelegate(delegateName)
31 | util.FailOnError(err)
32 | }
33 |
34 | func DeleteAllDelegates() {
35 | turandot := NewClient().Turandot()
36 | delegates, err := turandot.ListDelegates()
37 | util.FailOnError(err)
38 | for _, delegate := range delegates {
39 | log.Infof("deleting delegate: %s", delegate)
40 | err := turandot.DeleteDelegate(delegate)
41 | util.FailOnError(err)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/controller/parser/clout-attribute-value.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "github.com/tliron/go-transcribe"
5 | )
6 |
7 | //
8 | // CloutAttributeValue
9 | //
10 |
11 | type CloutAttributeValue struct {
12 | CapabilityName string `json:"capability"`
13 | AttributeName string `json:"attribute"`
14 | Value any `json:"value"`
15 | }
16 |
17 | //
18 | // CloutAttributeValueList
19 | //
20 |
21 | type CloutAttributeValueList []*CloutAttributeValue
22 |
23 | //
24 | // CloutAttributeValues
25 | //
26 |
27 | type CloutAttributeValues map[string]CloutAttributeValueList
28 |
29 | func NewCloutAttributeValues() CloutAttributeValues {
30 | return make(CloutAttributeValues)
31 | }
32 |
33 | func (self CloutAttributeValues) Set(vertexId string, capabilityName string, attributeName string, value any) {
34 | self[vertexId] = append(self[vertexId], &CloutAttributeValue{
35 | CapabilityName: capabilityName,
36 | AttributeName: attributeName,
37 | Value: value,
38 | })
39 | }
40 |
41 | func (self CloutAttributeValues) JSON() map[string]string {
42 | map_ := make(map[string]string)
43 | for vertexId, list := range self {
44 | if value, err := transcribe.NewTranscriber().StringifyJSON(list); err == nil {
45 | map_[vertexId] = value
46 | }
47 | }
48 | return map_
49 | }
50 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubevirt/1.0/js/kubernetes-resources-pre-get.js:
--------------------------------------------------------------------------------
1 |
2 | const tosca = require('tosca.lib.utils');
3 |
4 | exports.plugin = function() {
5 | for (let vertexId in clout.vertexes) {
6 | let vertex = clout.vertexes[vertexId];
7 | if (!tosca.isNodeTemplate(vertex))
8 | continue;
9 | let nodeTemplate = vertex.properties;
10 |
11 | for (let artifactName in nodeTemplate.artifacts) {
12 | let artifact = nodeTemplate.artifacts[artifactName];
13 |
14 | if ('cloud.puccini.kubevirt::CloudConfig' in artifact.types) {
15 | let cloudConfig = puccini.loadString(artifact.sourcePath);
16 |
17 | let variables = artifact.properties.variables;
18 | if (variables !== undefined) {
19 | variables = clout.newCoercible(variables, vertex);
20 | variables = variables.coerce();
21 | if (variables)
22 | for (let name in variables) {
23 | let r = new RegExp(escapeRegExp(name), 'g');
24 | cloudConfig = cloudConfig.replace(r, variables[name]);
25 | }
26 | }
27 |
28 | if (artifact.properties.base64)
29 | cloudConfig = puccini.btoa(cloudConfig);
30 |
31 | artifact.$artifact = cloudConfig;
32 | }
33 | }
34 | }
35 | }
36 |
37 | function escapeRegExp(s) {
38 | // See: https://stackoverflow.com/a/3561711
39 | return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
40 | }
41 |
--------------------------------------------------------------------------------
/controller/policy.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | contextpkg "context"
5 |
6 | "github.com/tliron/exturl"
7 | "github.com/tliron/turandot/controller/parser"
8 | resources "github.com/tliron/turandot/resources/turandot.puccini.cloud/v1alpha1"
9 | )
10 |
11 | func (self *Controller) processPolicies(context contextpkg.Context, policies parser.OrchestrationPolicies, service *resources.Service, urlContext *exturl.Context) error {
12 | for nodeTemplateName, nodePolicies := range policies {
13 | self.Log.Infof("processing policies for node template %s", nodeTemplateName)
14 | for _, policy := range nodePolicies {
15 | switch policy_ := policy.(type) {
16 | case *parser.OrchestrationProvisioningPolicy:
17 | self.Log.Infof("instantiable: %t", policy_.Instantiable)
18 | self.Log.Infof("substitutable: %t", policy_.Substitutable)
19 | self.Log.Infof("sites: %s", policy_.Sites)
20 |
21 | // TODO: should mode be defined in policy?
22 | mode := "normal"
23 |
24 | // Substitutions
25 | if policy_.Substitutable {
26 | for _, site := range policy_.Sites {
27 | if err := self.Substitute(context, service.Namespace, nodeTemplateName, policy_.SubstitutionInputs, mode, site, urlContext); err != nil {
28 | return err
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/turandot/commands/delegate-list.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "sort"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/tliron/go-ard"
8 | "github.com/tliron/kutil/terminal"
9 | "github.com/tliron/kutil/util"
10 | )
11 |
12 | func init() {
13 | delegateCommand.AddCommand(delegateListCommand)
14 | }
15 |
16 | var delegateListCommand = &cobra.Command{
17 | Use: "list",
18 | Short: "List delegates",
19 | Run: func(cmd *cobra.Command, args []string) {
20 | ListDelegates()
21 | },
22 | }
23 |
24 | func ListDelegates() {
25 | delegates, err := NewClient().Turandot().ListDelegates()
26 | util.FailOnError(err)
27 | if len(delegates) == 0 {
28 | return
29 | }
30 | sort.Strings(delegates)
31 |
32 | switch format {
33 | case "":
34 | // TODO fill table
35 | table := terminal.NewTable(maxWidth, "Name", "Server", "Namespace")
36 | for _, delegate := range delegates {
37 | table.Add(delegate, "TODO", "TODO")
38 | }
39 | table.Print()
40 |
41 | case "bare":
42 | for _, delegate := range delegates {
43 | terminal.Println(delegate)
44 | }
45 |
46 | default:
47 | list := make(ard.List, len(delegates))
48 | for index, delegate := range delegates {
49 | map_ := make(ard.StringMap)
50 | map_["Name"] = delegate
51 | map_["Server"] = ""
52 | map_["Namespace"] = ""
53 | list[index] = map_
54 | }
55 | Transcriber().Write(list)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/client/tls.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "crypto/x509"
5 | "fmt"
6 |
7 | "github.com/tliron/kutil/util"
8 | core "k8s.io/api/core/v1"
9 | meta "k8s.io/apimachinery/pkg/apis/meta/v1"
10 | )
11 |
12 | const tlsMountPath = "/tls"
13 |
14 | var tlsCertificatePath = fmt.Sprintf("%s/%s", tlsMountPath, core.TLSCertKey)
15 | var tlsKeyPath = fmt.Sprintf("%s/%s", tlsMountPath, core.TLSPrivateKeyKey)
16 |
17 | func (self *Client) GetSecret(namespace string, secretName string) (*core.Secret, error) {
18 | return self.Kubernetes.CoreV1().Secrets(namespace).Get(self.Context, secretName, meta.GetOptions{})
19 | }
20 |
21 | func (self *Client) GetSecretTLSCertPool(namespace string, secretName string, secretDataKey string) (*x509.CertPool, error) {
22 | if secret, err := self.GetSecret(namespace, secretName); err == nil {
23 | if secretDataKey == "" {
24 | secretDataKey = core.TLSCertKey
25 | }
26 |
27 | switch secret.Type {
28 | case core.SecretTypeTLS, core.SecretTypeServiceAccountToken:
29 | if bytes, ok := secret.Data[secretDataKey]; ok {
30 | return util.ParseX509CertificatePool(bytes)
31 | } else {
32 | return nil, fmt.Errorf("no data key %q in %q secret: %s", secretDataKey, secret.Type, secret.Data)
33 | }
34 |
35 | default:
36 | return nil, fmt.Errorf("unsupported TLS secret type: %s", secret.Type)
37 | }
38 | } else {
39 | return nil, err
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/scripts/build-asterisk-vnf-container-image:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # For virt-customize:
5 | # dnf install libguestfs-tools-c
6 |
7 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
8 | . "$HERE/../../../scripts/_env"
9 | . "$ROOT/scripts/_trap"
10 |
11 | # https://alt.fedoraproject.org/cloud/
12 |
13 | BASE_IMAGE=scratch
14 | DISK_NAME=Fedora-Cloud-Base-33-1.2.x86_64.qcow2
15 | URL=https://download.fedoraproject.org/pub/fedora/linux/releases/33/Cloud/x86_64/images/$DISK_NAME
16 | DISK=$TMPDIR/$DISK_NAME
17 | IMAGE=asterisk-vnf
18 | LOCAL=localhost/$IMAGE
19 | IMAGES=$(readlink --canonicalize "$HERE/../artifacts/images")
20 |
21 | m 'downloading base disk...'
22 | rm --force "$DISK"
23 | wget --quiet --show-progress --output-document="$DISK" "$URL"
24 |
25 | m 'installing asterisk packages...'
26 | virt-customize \
27 | --add "$DISK" \
28 | --selinux-relabel \
29 | --install asterisk,asterisk-pjsip,asterisk-sounds-core-en,asterisk-sounds-core-en-ulaw,tcpdump,grc
30 |
31 | CONTAINER_ID=$(buildah from "$BASE_IMAGE")
32 |
33 | buildah copy "$CONTAINER_ID" "$DISK" "/disk/$DISK_NAME"
34 |
35 | buildah config \
36 | --author Turandot \
37 | --created-by buildah \
38 | "$CONTAINER_ID"
39 |
40 | buildah commit "$CONTAINER_ID" "$LOCAL"
41 |
42 | mkdir --parents "$IMAGES"
43 |
44 | "$ROOT/scripts/save-portable-container-image" "$LOCAL" "$IMAGES/$IMAGE.tar.gz"
45 |
--------------------------------------------------------------------------------
/turandot/commands/logs.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 |
8 | "github.com/tliron/kutil/kubernetes"
9 | "github.com/tliron/kutil/util"
10 | "github.com/tliron/turandot/controller"
11 | )
12 |
13 | func Logs(appNameSuffix string, containerName string) {
14 | // TODO: what happens if we follow more than one log?
15 | readers, err := NewClient().Logs(appNameSuffix, containerName, tail, follow)
16 | util.FailOnError(err)
17 | for _, reader := range readers {
18 | defer reader.Close()
19 | }
20 | for _, reader := range readers {
21 | io.Copy(os.Stdout, reader)
22 | }
23 | }
24 |
25 | func (self *Client) Logs(appNameSuffix string, containerName string, tail int, follow bool) ([]io.ReadCloser, error) {
26 | appName := fmt.Sprintf("%s-%s", controller.NamePrefix, appNameSuffix)
27 |
28 | if podNames, err := kubernetes.GetPodNames(self.Context, self.Kubernetes, self.Namespace, appName); err == nil {
29 | readers := make([]io.ReadCloser, len(podNames))
30 | for index, podName := range podNames {
31 | if reader, err := kubernetes.Log(self.Context, self.Kubernetes, self.Namespace, podName, containerName, tail, follow); err == nil {
32 | readers[index] = reader
33 | } else {
34 | for i := 0; i < index; i++ {
35 | readers[i].Close()
36 | }
37 | return nil, err
38 | }
39 | }
40 | return readers, nil
41 | } else {
42 | return nil, err
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/scripts/asterisk/configure-vnf.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | cd /tmp
5 |
6 | if [ -f configured ]; then
7 | exit 0
8 | fi
9 |
10 | function ip_address () {
11 | # See: https://stackoverflow.com/a/26694162
12 | ip -family inet address show $1 | grep --only-matching --perl-regexp '(?<=inet\s)\d+(\.\d+){3}'
13 | }
14 |
15 | EXTERNAL_IP=$1
16 |
17 | if [ "$EXTERNAL_IP" == '' ]; then
18 | exit 1
19 | fi
20 |
21 | CONTROL_PLANE_IP=$(ip_address eth0)
22 | DATA_PLANE_IP=$(ip_address eth1)
23 | TRUNK_IP=192.68.1.1 # CNF
24 |
25 | cp /dev/stdin clout.yaml
26 |
27 | cat extensions.conf.template | __=\\$ TRUNK_IP=$TRUNK_IP envsubst > extensions.conf
28 | cat pjsip.conf.template | CONTROL_PLANE_IP=$CONTROL_PLANE_IP DATA_PLANE_IP=$DATA_PLANE_IP EXTERNAL_IP=$EXTERNAL_IP TRUNK_IP=$TRUNK_IP envsubst > pjsip.conf
29 |
30 | sudo mv pjsip.conf /etc/asterisk/pjsip.conf
31 | sudo mv rtp.conf /etc/asterisk/rtp.conf
32 | sudo mv extensions.conf /etc/asterisk/extensions.conf
33 |
34 | sudo chown asterisk:asterisk \
35 | /etc/asterisk/pjsip.conf \
36 | /etc/asterisk/rtp.conf \
37 | /etc/asterisk/extensions.conf
38 |
39 | sudo chcon system_u:object_r:asterisk_etc_t:s0 \
40 | /etc/asterisk/pjsip.conf \
41 | /etc/asterisk/rtp.conf \
42 | /etc/asterisk/extensions.conf
43 |
44 | sudo asterisk -rx 'core restart now'
45 | #sudo systemctl restart asterisk.service
46 |
47 | touch configured
48 |
--------------------------------------------------------------------------------
/turandot/commands/service-delete.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/tliron/kutil/util"
6 | )
7 |
8 | func init() {
9 | serviceCommand.AddCommand(serviceDeleteCommand)
10 | serviceDeleteCommand.Flags().BoolVarP(&all, "all", "a", false, "delete all services")
11 | }
12 |
13 | var serviceDeleteCommand = &cobra.Command{
14 | Use: "delete [[SERVICE NAME]]",
15 | Short: "Delete a deployed service (or all services)",
16 | Args: cobra.RangeArgs(0, 1),
17 | Run: func(cmd *cobra.Command, args []string) {
18 | if len(args) == 1 {
19 | serviceName := args[0]
20 | DeleteService(serviceName)
21 | } else if all {
22 | DeleteAllServices()
23 | } else {
24 | util.Fail("must provide service name or specify \"--all\"")
25 | }
26 | },
27 | }
28 |
29 | func DeleteService(serviceName string) {
30 | // TODO: in cluster mode we must specify the namespace
31 | namespace := ""
32 |
33 | err := NewClient().Turandot().DeleteService(namespace, serviceName)
34 | util.FailOnError(err)
35 | }
36 |
37 | func DeleteAllServices() {
38 | turandot := NewClient().Turandot()
39 | services, err := turandot.ListServices()
40 | util.FailOnError(err)
41 | for _, service := range services.Items {
42 | log.Infof("deleting service: %s/%s", service.Namespace, service.Name)
43 | err := turandot.DeleteService(service.Namespace, service.Name)
44 | util.FailOnError(err)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/helm/1.0/nodes.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | node_types:
4 |
5 | Release:
6 | description: >-
7 | Helm release.
8 | properties:
9 | name:
10 | description: >-
11 | Release name. If not specified will use node template name.
12 | type: string
13 | required: false
14 | chart:
15 | description: >-
16 | URL to the chart (as with "helm install"). Can point to an archive (tarball) or path to
17 | a local directory. This can be a "get_artifact" function call (see the Chart artifact
18 | type).
19 | type: string
20 | version:
21 | type: string
22 | required: false
23 | namespace:
24 | type: string
25 | required: false
26 | hooks:
27 | description: >-
28 | Set to true to install hooks.
29 | type: boolean
30 | default: false
31 | values:
32 | description: >-
33 | Set chart values (as with "helm install --set-string"). If both "values" and "valuesUrl"
34 | are used and overlap, "values" will override "valuesUrl".
35 | type: map
36 | entry_schema: string
37 | required: false
38 | valuesUrl:
39 | description: >-
40 | URL to a YAML file with chart values (as with "helm install --values").
41 | type: string
42 | required: false
43 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/asterisk/pjsip-cnf.conf.template:
--------------------------------------------------------------------------------
1 | [local]
2 | type=transport
3 | allow_reload=true
4 | protocol=udp
5 | bind=$CONTROL_PLANE_IP
6 | external_media_address=$EXTERNAL_IP
7 |
8 | [external]
9 | type=transport
10 | allow_reload=true
11 | protocol=udp
12 | bind=$DATA_PLANE_IP
13 |
14 | ; templates
15 |
16 | [endpoint](!)
17 | type=endpoint
18 | context=default
19 | transport=local
20 | disallow=all
21 | allow=ulaw
22 |
23 | [auth](!)
24 | type=auth
25 | auth_type=userpass
26 | password=password
27 |
28 | [aor](!)
29 | type=aor
30 | max_contacts=5
31 | remove_existing=true
32 |
33 | ; anonymous
34 | ; (handled by es_pjsip_endpoint_identifier_anonymous.so)
35 |
36 | [anonymous](endpoint)
37 |
38 | ; 100
39 |
40 | [100](endpoint)
41 | auth=100
42 | aors=100
43 |
44 | [100](auth)
45 | username=100
46 |
47 | [100](aor)
48 |
49 | ; 101
50 |
51 | [101](endpoint)
52 | auth=101
53 | aors=101
54 |
55 | [101](auth)
56 | username=101
57 |
58 | [101](aor)
59 |
60 | ; trunk
61 |
62 | [trunk]
63 | type=registration
64 | transport=external
65 | outbound_auth=trunk
66 | server_uri=sip:trunk@$TRUNK_IP
67 | client_uri=sip:trunk@$TRUNK_IP
68 |
69 | [trunk]
70 | type=identify
71 | endpoint=trunk
72 | match=$TRUNK_IP
73 |
74 | [trunk](endpoint)
75 | transport=external
76 | media_address=$DATA_PLANE_IP
77 | outbound_auth=trunk
78 | aors=trunk
79 |
80 | [trunk](auth)
81 | username=trunk
82 |
83 | [trunk](aor)
84 | contact=sip:$TRUNK_IP
85 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/asterisk/pjsip-vnf.conf.template:
--------------------------------------------------------------------------------
1 | [local]
2 | type=transport
3 | allow_reload=true
4 | protocol=udp
5 | bind=$CONTROL_PLANE_IP
6 | external_media_address=$EXTERNAL_IP
7 |
8 | [external]
9 | type=transport
10 | allow_reload=true
11 | protocol=udp
12 | bind=$DATA_PLANE_IP
13 |
14 | ; templates
15 |
16 | [endpoint](!)
17 | type=endpoint
18 | context=default
19 | transport=local
20 | disallow=all
21 | allow=ulaw
22 |
23 | [auth](!)
24 | type=auth
25 | auth_type=userpass
26 | password=password
27 |
28 | [aor](!)
29 | type=aor
30 | max_contacts=5
31 | remove_existing=true
32 |
33 | ; anonymous
34 | ; (handled by es_pjsip_endpoint_identifier_anonymous.so)
35 |
36 | [anonymous](endpoint)
37 |
38 | ; 200
39 |
40 | [200](endpoint)
41 | auth=200
42 | aors=200
43 |
44 | [200](auth)
45 | username=200
46 |
47 | [200](aor)
48 |
49 | ; 201
50 |
51 | [201](endpoint)
52 | auth=201
53 | aors=201
54 |
55 | [201](auth)
56 | username=201
57 |
58 | [201](aor)
59 |
60 | ; trunk
61 |
62 | [trunk]
63 | type=registration
64 | transport=external
65 | outbound_auth=trunk
66 | server_uri=sip:trunk@$TRUNK_IP
67 | client_uri=sip:trunk@$TRUNK_IP
68 |
69 | [trunk]
70 | type=identify
71 | endpoint=trunk
72 | match=$TRUNK_IP
73 |
74 | [trunk](endpoint)
75 | transport=external
76 | media_address=$DATA_PLANE_IP
77 | outbound_auth=trunk
78 | aors=trunk
79 |
80 | [trunk](auth)
81 | username=trunk
82 |
83 | [trunk](aor)
84 | contact=sip:$TRUNK_IP
85 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/scripts/build-csars:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | mkdir --parents "$ROOT/dist"
9 |
10 | function build () {
11 | local ARCHIVE=$1
12 | local WORK=$(mktemp --directory)
13 |
14 | pushd "$HERE/.." > /dev/null
15 | rsync --recursive --relative "${@:2}" "$WORK/"
16 | popd > /dev/null
17 |
18 | pushd "$ROOT/assets/tosca" > /dev/null
19 | rsync --recursive --relative profiles "$WORK/"
20 | popd > /dev/null
21 |
22 | puccini-csar "$ROOT/dist/$ARCHIVE.csar" "$WORK"
23 |
24 | rm --recursive --force "$WORK"
25 |
26 | m "built $ROOT/dist/$ARCHIVE.csar"
27 | }
28 |
29 | mkdir --parents "$HERE/../artifacts/binaries/"
30 | cp "$(which puccini-clout)" "$HERE/../artifacts/binaries/"
31 |
32 | build telephony-network-service \
33 | telephony-network-service.yaml \
34 | profiles
35 |
36 | build asterisk-cnf \
37 | asterisk-cnf.yaml \
38 | profiles \
39 | artifacts/scripts \
40 | artifacts/binaries \
41 | artifacts/asterisk \
42 | artifacts/images/asterisk-cnf.tar.gz
43 |
44 | build asterisk-vnf \
45 | asterisk-vnf.yaml \
46 | profiles \
47 | artifacts/scripts \
48 | artifacts/binaries \
49 | artifacts/asterisk \
50 | artifacts/keypairs \
51 | artifacts/cloud-config \
52 | artifacts/images/asterisk-vnf.tar.gz
53 |
54 | build simple-data-plane \
55 | simple-data-plane.yaml \
56 | profiles
57 |
--------------------------------------------------------------------------------
/resources/turandot.puccini.cloud/v1alpha1/scheme.go:
--------------------------------------------------------------------------------
1 | package v1alpha1
2 |
3 | import (
4 | meta "k8s.io/apimachinery/pkg/apis/meta/v1"
5 | "k8s.io/apimachinery/pkg/runtime"
6 | "k8s.io/apimachinery/pkg/runtime/schema"
7 |
8 | group "github.com/tliron/turandot/resources/turandot.puccini.cloud"
9 | )
10 |
11 | // Group version used to register these objects
12 | // Note: Generator *requires* it to be called "SchemeGroupVersion"
13 | var SchemeGroupVersion = schema.GroupVersion{Group: group.GroupName, Version: Version}
14 |
15 | // Takes an unqualified kind and returns a group-qualified GroupKind
16 | // Note: Generator *requires* it to be called "Kind"
17 | func Kind(kind string) schema.GroupKind {
18 | return SchemeGroupVersion.WithKind(kind).GroupKind()
19 | }
20 |
21 | // Takes an unqualified resource and returns a group-qualified GroupResource
22 | // Note: Generator *requires* it to be called "Resource"
23 | func Resource(resource string) schema.GroupResource {
24 | return SchemeGroupVersion.WithResource(resource).GroupResource()
25 | }
26 |
27 | // Registers this API group and version to a scheme
28 | // Note: Generator *requires* it to be called "AddToScheme"
29 | var AddToScheme = schemeBuilder.AddToScheme
30 |
31 | var schemeBuilder = runtime.NewSchemeBuilder(func(scheme *runtime.Scheme) error {
32 | scheme.AddKnownTypes(
33 | SchemeGroupVersion,
34 | &Service{},
35 | &ServiceList{},
36 | )
37 | meta.AddToGroupVersion(scheme, SchemeGroupVersion)
38 | return nil
39 | })
40 |
--------------------------------------------------------------------------------
/apis/clientset/versioned/fake/register.go:
--------------------------------------------------------------------------------
1 | // Code generated by client-gen. DO NOT EDIT.
2 |
3 | package fake
4 |
5 | import (
6 | turandotv1alpha1 "github.com/tliron/turandot/resources/turandot.puccini.cloud/v1alpha1"
7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8 | runtime "k8s.io/apimachinery/pkg/runtime"
9 | schema "k8s.io/apimachinery/pkg/runtime/schema"
10 | serializer "k8s.io/apimachinery/pkg/runtime/serializer"
11 | utilruntime "k8s.io/apimachinery/pkg/util/runtime"
12 | )
13 |
14 | var scheme = runtime.NewScheme()
15 | var codecs = serializer.NewCodecFactory(scheme)
16 |
17 | var localSchemeBuilder = runtime.SchemeBuilder{
18 | turandotv1alpha1.AddToScheme,
19 | }
20 |
21 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition
22 | // of clientsets, like in:
23 | //
24 | // import (
25 | // "k8s.io/client-go/kubernetes"
26 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme"
27 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
28 | // )
29 | //
30 | // kclientset, _ := kubernetes.NewForConfig(c)
31 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
32 | //
33 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
34 | // correctly.
35 | var AddToScheme = localSchemeBuilder.AddToScheme
36 |
37 | func init() {
38 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
39 | utilruntime.Must(AddToScheme(scheme))
40 | }
41 |
--------------------------------------------------------------------------------
/turandot/commands/delegate-set.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "os/user"
5 | "path/filepath"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/tliron/kutil/util"
9 | )
10 |
11 | var delegateKubeconfigPath string
12 | var delegateContext string
13 | var delegateNamespace string
14 |
15 | func init() {
16 | var defaultKubeconfigPath string
17 | if u, err := user.Current(); err == nil {
18 | defaultKubeconfigPath = filepath.Join(u.HomeDir, ".kube", "config")
19 | }
20 |
21 | delegateCommand.AddCommand(delegateSetCommand)
22 | delegateSetCommand.Flags().StringVar(&delegateKubeconfigPath, "delegate-kubeconfig", defaultKubeconfigPath, "path to delegate delegate Kubernetes configuration")
23 | delegateSetCommand.Flags().StringVar(&delegateContext, "delegate-context", "", "override current context in delegate Kubernetes configuration")
24 | delegateSetCommand.Flags().StringVar(&delegateNamespace, "delegate-namespace", "", "namespace (overrides context namespace in delegate Kubernetes configuration)")
25 | }
26 |
27 | var delegateSetCommand = &cobra.Command{
28 | Use: "set [DELEGATE NAME]",
29 | Short: "Update or create a delegate",
30 | Args: cobra.ExactArgs(1),
31 | Run: func(cmd *cobra.Command, args []string) {
32 | delegateName := args[0]
33 | SetDelegate(delegateName)
34 | },
35 | }
36 |
37 | func SetDelegate(delegateName string) {
38 | err := NewClient().Turandot().SetDelegate(delegateName, delegateKubeconfigPath, delegateContext, delegateNamespace)
39 | util.FailOnError(err)
40 | }
41 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/profiles/network-service/profile.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | namespace: cloud.puccini.network-service
4 |
5 | node_types:
6 |
7 | NetworkPlane:
8 | properties:
9 | annotations:
10 | type: map
11 | entry_schema: string
12 | required: false
13 | capabilities:
14 | connection: Connectable
15 |
16 | NetworkFunction:
17 | properties:
18 | annotations:
19 | type: map
20 | entry_schema: string
21 | required: false
22 | capabilities:
23 | deployment: Deployable
24 | requirements:
25 | - connection:
26 | capability: Connectable
27 | relationship: Connection
28 |
29 | Router:
30 | derived_from: NetworkFunction
31 | capabilities:
32 | route: Routable
33 |
34 | capability_types:
35 |
36 | Connectable: {}
37 |
38 | Routable:
39 | derived_from: Connectable
40 |
41 | Deployable:
42 | attributes:
43 | ingress:
44 | type: Route
45 |
46 | relationship_types:
47 |
48 | Connection: {}
49 |
50 | Routing:
51 | derived_from: Connection
52 | properties:
53 | routes:
54 | type: list
55 | entry_schema: Route
56 |
57 | data_types:
58 |
59 | NetworkSubnet:
60 | derived_from: string
61 |
62 | NetworkAddress:
63 | derived_from: string
64 |
65 | Route:
66 | properties:
67 | destination:
68 | type: NetworkSubnet
69 | gateway:
70 | type: NetworkAddress
71 |
--------------------------------------------------------------------------------
/apis/clientset/versioned/scheme/register.go:
--------------------------------------------------------------------------------
1 | // Code generated by client-gen. DO NOT EDIT.
2 |
3 | package scheme
4 |
5 | import (
6 | turandotv1alpha1 "github.com/tliron/turandot/resources/turandot.puccini.cloud/v1alpha1"
7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8 | runtime "k8s.io/apimachinery/pkg/runtime"
9 | schema "k8s.io/apimachinery/pkg/runtime/schema"
10 | serializer "k8s.io/apimachinery/pkg/runtime/serializer"
11 | utilruntime "k8s.io/apimachinery/pkg/util/runtime"
12 | )
13 |
14 | var Scheme = runtime.NewScheme()
15 | var Codecs = serializer.NewCodecFactory(Scheme)
16 | var ParameterCodec = runtime.NewParameterCodec(Scheme)
17 | var localSchemeBuilder = runtime.SchemeBuilder{
18 | turandotv1alpha1.AddToScheme,
19 | }
20 |
21 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition
22 | // of clientsets, like in:
23 | //
24 | // import (
25 | // "k8s.io/client-go/kubernetes"
26 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme"
27 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
28 | // )
29 | //
30 | // kclientset, _ := kubernetes.NewForConfig(c)
31 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
32 | //
33 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
34 | // correctly.
35 | var AddToScheme = localSchemeBuilder.AddToScheme
36 |
37 | func init() {
38 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
39 | utilruntime.Must(AddToScheme(Scheme))
40 | }
41 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/orchestration/1.0/interfaces.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | imports:
4 |
5 | - data.yaml
6 |
7 | interface_types:
8 |
9 | # Execution
10 |
11 | Execution:
12 | description: >-
13 | Base type for executions.
14 | inputs:
15 | mode:
16 | description: >-
17 | If not specified then the interface name will be used. If the interface names has "."
18 | in it then the last "." and what follows it will be stripped. E.g. interface
19 | "normal.1" will have mode "normal".
20 |
21 | This makes it easy to specify several executions for the same mode. Note that
22 | executions will occur in alphanumeric sorting order of the interface names.
23 | type: string
24 | required: false
25 | requirements:
26 | type: ExecutionRequirements
27 | required: false
28 |
29 | Scriptlet:
30 | description: >-
31 | Executes a scriptlet within the Clout.
32 | derived_from: Execution
33 | inputs:
34 | scriptlet:
35 | description: >-
36 | Scriptlet name.
37 | type: string
38 | arguments:
39 | description: >-
40 | Scriptlet arguments.
41 |
42 | Implicit arguments:
43 |
44 | * "service" is the name of the service.
45 | * "nodeTemplate" is the name of the node template.
46 |
47 | TODO: in the case of interfaces on relationships?
48 | type: map
49 | entry_schema: string
50 | required: false
51 |
--------------------------------------------------------------------------------
/controller/events.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "fmt"
5 |
6 | resources "github.com/tliron/turandot/resources/turandot.puccini.cloud/v1alpha1"
7 | core "k8s.io/api/core/v1"
8 | )
9 |
10 | const (
11 | EventCompiled = "Compiled"
12 | EventCompilationError = "CompilationError"
13 | EventInstantiated = "Instantiated"
14 | EventInstantiationError = "InstantiationError"
15 | EventCloutUpdateError = "CloutUpdateError"
16 | )
17 |
18 | func (self *Controller) EventCompiled(service *resources.Service) {
19 | self.Events.Event(service, core.EventTypeNormal, EventCompiled, "Service template compiled successfully")
20 | }
21 |
22 | func (self *Controller) EventCompilationError(service *resources.Service, err error) {
23 | self.Events.Event(service, core.EventTypeWarning, EventCompilationError, fmt.Sprintf("Service template compilation error: %s", err.Error()))
24 | }
25 |
26 | func (self *Controller) EventInstantiated(service *resources.Service) {
27 | self.Events.Event(service, core.EventTypeNormal, EventInstantiated, "Service instantiated successfully")
28 | }
29 |
30 | func (self *Controller) EventInstantiationError(service *resources.Service, err error) {
31 | self.Events.Event(service, core.EventTypeWarning, EventInstantiationError, fmt.Sprintf("Service instantiation error: %s", err.Error()))
32 | }
33 |
34 | func (self *Controller) EventCloutUpdateError(service *resources.Service, err error) {
35 | self.Events.Event(service, core.EventTypeWarning, EventCloutUpdateError, fmt.Sprintf("Service Clout update error: %s", err.Error()))
36 | }
37 |
--------------------------------------------------------------------------------
/client/registry.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | reposure "github.com/tliron/reposure/resources/reposure.puccini.cloud/v1alpha1"
8 | )
9 |
10 | const serviceTemplateArtifactCategory = "service-templates"
11 |
12 | func (self *Client) GetRegistryURLForCSAR(registry *reposure.Registry, artifactName string) (string, error) {
13 | if address, err := self.Reposure.RegistryClient().GetHost(registry); err == nil {
14 | return fmt.Sprintf("docker://%s/%s?format=csar", address, artifactName), nil
15 | } else {
16 | return "", err
17 | }
18 | }
19 |
20 | func (self *Client) GetRegistryServiceTemplateURL(registry *reposure.Registry, serviceTemplateName string) (string, error) {
21 | return self.GetRegistryURLForCSAR(registry, self.RegistryImageNameForServiceTemplateName(serviceTemplateName))
22 | }
23 |
24 | // Utils
25 |
26 | func (self *Client) RegistryImageNameForServiceTemplateName(serviceTemplateName string) string {
27 | // Note: OpenShift registry permissions require the namespace as the tag category
28 | return fmt.Sprintf("%s/%s-%s", self.Namespace, serviceTemplateArtifactCategory, serviceTemplateName)
29 | }
30 |
31 | func (self *Client) ServiceTemplateNameForRegistryImageName(artifactName string) (string, bool) {
32 | prefix := fmt.Sprintf("%s/%s-", self.Namespace, serviceTemplateArtifactCategory)
33 | if strings.HasPrefix(artifactName, prefix) {
34 | name := artifactName[len(prefix):]
35 | if colon := strings.Index(name, ":"); colon != -1 {
36 | name = name[:colon]
37 | }
38 | return name, true
39 | } else {
40 | return "", false
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/README.md:
--------------------------------------------------------------------------------
1 | Kubernetes Profile
2 | ==================
3 |
4 | Node Types
5 | ----------
6 |
7 | Kubernetes Metadata
8 | -------------------
9 |
10 | Capability Type Metadata
11 | ------------------------
12 |
13 | `turandot.apiVersion`: Kubernetes resource API version. This metadata value triggers a Kubernetes
14 | resource generation.
15 |
16 | `turandot.kind`: Kubernetes resource kind. If not specified will default to the capability type name.
17 |
18 | `turandot.move[.#]`: Move a value or branch in the resource. The format is "->".
19 | Adding "." to the key is optional and used for ensuring the order of move operations, as the keys
20 | are sorted alphabetically before processing.
21 |
22 | `turandot.copy[.#]`: Copy a value or branch in the resource. The format is "->".
23 | Adding "." to the key is optional and used for ensuring the order of move operations, as the keys
24 | are sorted alphabetically before processing. Note that moves are processed before copies.
25 |
26 | Data Type Metadata
27 | ------------------
28 |
29 | Note that TOSCA scalar-unit types will always be converted to numbers (floats or integers as
30 | appropriate).
31 |
32 | Property Metadata
33 | -----------------
34 |
35 | `puccini.data-type:turandot.ignore`: When "true" (a string) will *not* export the property to the
36 | Kubernetes resource.
37 |
38 | Attribute Metadata
39 | ------------------
40 |
41 | `puccini.data-type:turandot.mapping`: The value is the path within the Kubernetes resource from which to
42 | map the attribute value. Note that both "status." and "spec." can be used.
43 |
--------------------------------------------------------------------------------
/examples/helm/README.md:
--------------------------------------------------------------------------------
1 | Helm Example
2 | ============
3 |
4 | This example demonstrates a Helm chart included as an artifact within a CSAR.
5 |
6 | To deploy this example you can generally follow the instructions for the
7 | [Hello World example](../../TUTORIAL.md), though make sure to build the chart
8 | artifact first. E.g.:
9 |
10 | examples/scripts/build-chart
11 | examples/scripts/build-csar
12 | turandot service deploy helm --file=dist/helm.csar
13 |
14 | As long as you have LoadBalancer ingress support on your cluster (such as
15 | Minikube's "tunnel"), then you can then use curl or a web browser to access the
16 | deployed service:
17 |
18 | IP=$(kubectl get services --selector=app.kubernetes.io/name=helm --output=jsonpath={.items[0].status.loadBalancer.ingress[0].ip})
19 | xdg-open http://$IP:8080
20 |
21 |
22 | Helper Scripts
23 | --------------
24 |
25 | * [Package the Helm chart](scripts/build-chart)
26 | * [Package as CSAR file](scripts/build-csar)
27 |
28 |
29 | How the Helm Chart Was Created
30 | ------------------------------
31 |
32 | We used `helm create hello-world`. Unfortunately, this example chart assumes privileged containers on
33 | the host that are not allowed out-of-the-box on OpenShift. To ensure it would work on OpenShift we
34 | made two changes:
35 |
36 | * In `value.yaml` we changed `image.repository` to `bitnami/nginx`, which points to an NGINX container
37 | image that does not require a priveleged container
38 | ([documentation](https://hub.docker.com/r/bitnami/nginx)).
39 | * In `templates/deployment.yaml` we changed the port from 80 to 8080 (again, because port 80 requires
40 | a privileged container).
41 |
--------------------------------------------------------------------------------
/apis/informers/externalversions/generic.go:
--------------------------------------------------------------------------------
1 | // Code generated by informer-gen. DO NOT EDIT.
2 |
3 | package externalversions
4 |
5 | import (
6 | "fmt"
7 |
8 | v1alpha1 "github.com/tliron/turandot/resources/turandot.puccini.cloud/v1alpha1"
9 | schema "k8s.io/apimachinery/pkg/runtime/schema"
10 | cache "k8s.io/client-go/tools/cache"
11 | )
12 |
13 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other
14 | // sharedInformers based on type
15 | type GenericInformer interface {
16 | Informer() cache.SharedIndexInformer
17 | Lister() cache.GenericLister
18 | }
19 |
20 | type genericInformer struct {
21 | informer cache.SharedIndexInformer
22 | resource schema.GroupResource
23 | }
24 |
25 | // Informer returns the SharedIndexInformer.
26 | func (f *genericInformer) Informer() cache.SharedIndexInformer {
27 | return f.informer
28 | }
29 |
30 | // Lister returns the GenericLister.
31 | func (f *genericInformer) Lister() cache.GenericLister {
32 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)
33 | }
34 |
35 | // ForResource gives generic access to a shared informer of the matching type
36 | // TODO extend this to unknown resources with a client pool
37 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
38 | switch resource {
39 | // Group=turandot.puccini.cloud, Version=v1alpha1
40 | case v1alpha1.SchemeGroupVersion.WithResource("services"):
41 | return &genericInformer{resource: resource.GroupResource(), informer: f.Turandot().V1alpha1().Services().Informer()}, nil
42 |
43 | }
44 |
45 | return nil, fmt.Errorf("no informer found for %v", resource)
46 | }
47 |
--------------------------------------------------------------------------------
/apis/applyconfiguration/turandot.puccini.cloud/v1alpha1/servicetemplate.go:
--------------------------------------------------------------------------------
1 | // Code generated by applyconfiguration-gen. DO NOT EDIT.
2 |
3 | package v1alpha1
4 |
5 | // ServiceTemplateApplyConfiguration represents an declarative configuration of the ServiceTemplate type for use
6 | // with apply.
7 | type ServiceTemplateApplyConfiguration struct {
8 | Direct *ServiceTemplateDirectApplyConfiguration `json:"direct,omitempty"`
9 | Indirect *ServiceTemplateIndirectApplyConfiguration `json:"indirect,omitempty"`
10 | }
11 |
12 | // ServiceTemplateApplyConfiguration constructs an declarative configuration of the ServiceTemplate type for use with
13 | // apply.
14 | func ServiceTemplate() *ServiceTemplateApplyConfiguration {
15 | return &ServiceTemplateApplyConfiguration{}
16 | }
17 |
18 | // WithDirect sets the Direct field in the declarative configuration to the given value
19 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
20 | // If called multiple times, the Direct field is set to the value of the last call.
21 | func (b *ServiceTemplateApplyConfiguration) WithDirect(value *ServiceTemplateDirectApplyConfiguration) *ServiceTemplateApplyConfiguration {
22 | b.Direct = value
23 | return b
24 | }
25 |
26 | // WithIndirect sets the Indirect field in the declarative configuration to the given value
27 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
28 | // If called multiple times, the Indirect field is set to the value of the last call.
29 | func (b *ServiceTemplateApplyConfiguration) WithIndirect(value *ServiceTemplateIndirectApplyConfiguration) *ServiceTemplateApplyConfiguration {
30 | b.Indirect = value
31 | return b
32 | }
33 |
--------------------------------------------------------------------------------
/turandot-profile-generator/configuration.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | //
4 | // Configuration
5 | //
6 |
7 | type Configuration struct {
8 | Name string `yaml:"name"`
9 | Version string `yaml:"version"`
10 | OpenAPI string `yaml:"open-api"`
11 | ReferenceURL string `yaml:"reference-url"`
12 | OutputDir string `yaml:"output-dir"`
13 | Groups Groups `yaml:"groups"`
14 | Exclude []string `yaml:"exclude"`
15 | Rename map[string]string `yaml:"rename"`
16 | Add map[string]*Type `yaml:"add"`
17 | Override map[string]*Type `yaml:"override"`
18 | }
19 |
20 | //
21 | // Groups
22 | //
23 |
24 | type Groups struct {
25 | Default string `yaml:"default"`
26 | Imports map[string]Import `yaml:"imports"`
27 | }
28 |
29 | //
30 | // Import
31 | //
32 |
33 | type Import struct {
34 | NamespacePrefix string `yaml:"namespace_prefix"`
35 | File string `yaml:"file"`
36 | }
37 |
38 | //
39 | // Type
40 | //
41 |
42 | type Type struct {
43 | Entity string `yaml:"entity"`
44 | Metadata []map[string]string `yaml:"metadata"`
45 | Description string `yaml:"description"`
46 | Fields map[string]*Field `yaml:"fields"`
47 | DerivedFrom string `yaml:"derived_from"`
48 | Derive map[string][]string `yaml:"derive"`
49 | }
50 |
51 | //
52 | // Field
53 | //
54 |
55 | type Field struct {
56 | Type string `yaml:"type"`
57 | EntrySchema string `yaml:"entry_schema"`
58 | Description string `yaml:"description"`
59 | Default string `yaml:"default"`
60 | Required *bool `yaml:"required"`
61 | Constraints []string `yaml:"constraints"`
62 | }
63 |
--------------------------------------------------------------------------------
/apis/applyconfiguration/utils.go:
--------------------------------------------------------------------------------
1 | // Code generated by applyconfiguration-gen. DO NOT EDIT.
2 |
3 | package applyconfiguration
4 |
5 | import (
6 | turandotpuccinicloudv1alpha1 "github.com/tliron/turandot/apis/applyconfiguration/turandot.puccini.cloud/v1alpha1"
7 | v1alpha1 "github.com/tliron/turandot/resources/turandot.puccini.cloud/v1alpha1"
8 | schema "k8s.io/apimachinery/pkg/runtime/schema"
9 | )
10 |
11 | // ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no
12 | // apply configuration type exists for the given GroupVersionKind.
13 | func ForKind(kind schema.GroupVersionKind) interface{} {
14 | switch kind {
15 | // Group=turandot.puccini.cloud, Version=v1alpha1
16 | case v1alpha1.SchemeGroupVersion.WithKind("Service"):
17 | return &turandotpuccinicloudv1alpha1.ServiceApplyConfiguration{}
18 | case v1alpha1.SchemeGroupVersion.WithKind("ServiceNodeModeState"):
19 | return &turandotpuccinicloudv1alpha1.ServiceNodeModeStateApplyConfiguration{}
20 | case v1alpha1.SchemeGroupVersion.WithKind("ServiceSpec"):
21 | return &turandotpuccinicloudv1alpha1.ServiceSpecApplyConfiguration{}
22 | case v1alpha1.SchemeGroupVersion.WithKind("ServiceStatus"):
23 | return &turandotpuccinicloudv1alpha1.ServiceStatusApplyConfiguration{}
24 | case v1alpha1.SchemeGroupVersion.WithKind("ServiceTemplate"):
25 | return &turandotpuccinicloudv1alpha1.ServiceTemplateApplyConfiguration{}
26 | case v1alpha1.SchemeGroupVersion.WithKind("ServiceTemplateDirect"):
27 | return &turandotpuccinicloudv1alpha1.ServiceTemplateDirectApplyConfiguration{}
28 | case v1alpha1.SchemeGroupVersion.WithKind("ServiceTemplateIndirect"):
29 | return &turandotpuccinicloudv1alpha1.ServiceTemplateIndirectApplyConfiguration{}
30 |
31 | }
32 | return nil
33 | }
34 |
--------------------------------------------------------------------------------
/lab/minikube/test-multi:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../../scripts/_env"
6 | . "$ROOT/scripts/_trap"
7 |
8 | minikube profile edge
9 | kubectl config set-context edge --namespace=workspace
10 | turandot operator uninstall --wait -v
11 | turandot repository uninstall --wait -v
12 | kubectl delete events --all
13 |
14 | minikube profile central
15 | kubectl config set-context central --namespace=workspace
16 | turandot operator uninstall --wait -v
17 | turandot repository uninstall --wait -v
18 | kubectl delete events --all
19 |
20 | if [ "$1" == -b ]; then
21 | "$ROOT/scripts/build-container-image"
22 | "$ROOT/scripts/publish-container-image"
23 | # Reminder: clean ~/.local/share/containers/ occassionally!
24 | fi
25 |
26 | turandot operator install --site=central --wait -v
27 | turandot repository install --wait -v
28 | turandot repository create default --provider=turandot --wait -v
29 | #turandot repository create default --provider=minikube --wait -v
30 |
31 | turandot service deploy hello-world --file=dist/hello-world.csar -v
32 | turandot service deploy helm-hello-world --file=dist/helm-hello-world.csar -v
33 |
34 | turandot delegate set edge --delegate-context=edge -v
35 |
36 | turandot template register telephony-network-service --file=dist/telephony-network-service.csar -v
37 | turandot template register simple-data-plane --file=dist/simple-data-plane.csar -v
38 | turandot template register asterisk-cnf --file=dist/asterisk-cnf.csar -v
39 | turandot template register asterisk-vnf --file=dist/asterisk-vnf.csar -v
40 |
41 | turandot service deploy telephony-network-service --template=telephony-network-service --input=namespace=workspace -v
42 |
43 | turandot operator logs --follow
44 |
--------------------------------------------------------------------------------
/lab/connect-kube-dns:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/../scripts/_env"
6 |
7 | # Do we really need a new service?
8 | # Unfortunately, yes: port-forward will only work with TCP, but we need UDP for proper DNS support
9 | #kubectl port-forward service/kube-dns --namespace=kube-system 5353:53
10 |
11 | m 'exposing kube-dns...'
12 | cat <<- EOT | kubectl apply -f -
13 | kind: Service
14 | apiVersion: v1
15 |
16 | metadata:
17 | name: kube-dns-external
18 | namespace: kube-system
19 | labels:
20 | k8s-app: kube-dns
21 |
22 | spec:
23 | type: LoadBalancer
24 | selector:
25 | k8s-app: kube-dns
26 | ports:
27 | - name: dns
28 | protocol: UDP
29 | port: 53
30 | targetPort: 53
31 | EOT
32 |
33 | m 'waiting for kube-dns-external IP...'
34 | while [ -z "$IP" ]; do
35 | IP=$(kubectl get services kube-dns-external --namespace=kube-system --output=jsonpath={.status.loadBalancer.ingress[0].ip})
36 | sleep 1
37 | done
38 |
39 | m $IP
40 |
41 | m 'enabling dnsmasq plugin for NetworkManager...'
42 | cat <<- EOT | sudo tee /etc/NetworkManager/conf.d/00-dnsmasq.conf > /dev/null
43 | [main]
44 | dns=dnsmasq
45 | EOT
46 |
47 | # http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html
48 | cat <<- EOT | sudo tee /etc/NetworkManager/dnsmasq.d/minikube.conf > /dev/null
49 | server=/cluster.local/$IP
50 | addn-hosts=/etc/hosts
51 | EOT
52 |
53 | sudo systemctl reload NetworkManager.service
54 |
55 | # If we were trying to use localhost#5353 (in the port-forward situation) we would also need to
56 | # change SELinux permissions:
57 | #semanage permissive --add dnsmasq_t
58 |
59 | # test
60 | #dig @127.0.0.1 a asterisk-vnf-tcp.workspace.svc.cluster.local
61 | #host asterisk-vnf-tcp.workspace.svc.cluster.local
62 |
--------------------------------------------------------------------------------
/turandot-profile-generator/version.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "strconv"
5 | "strings"
6 | "unicode"
7 | )
8 |
9 | func isVersionNewer(newer string, older string) bool {
10 | if newer_ := NewVersion(newer); newer_ != nil {
11 | if older_ := NewVersion(older); older_ != nil {
12 | return newer_.IsNewer(older_)
13 | }
14 | }
15 | return false
16 | }
17 |
18 | //
19 | // Version
20 | //
21 |
22 | type Version struct {
23 | Major int
24 | Minor int
25 | Patch int
26 | }
27 |
28 | // e.g. v1alpha2
29 | func NewVersion(text string) *Version {
30 | if strings.HasPrefix(text, "v") {
31 | phase := 1
32 | var major, modifier, patch string
33 | for _, rune_ := range text[1:] {
34 | if phase == 1 {
35 | if unicode.IsDigit(rune_) {
36 | major += string(rune_)
37 | } else {
38 | phase = 2
39 | }
40 | }
41 |
42 | if phase == 2 {
43 | if unicode.IsDigit(rune_) {
44 | phase = 3
45 | } else {
46 | modifier += string(rune_)
47 | }
48 | }
49 |
50 | if phase == 3 {
51 | patch += string(rune_)
52 | }
53 | }
54 |
55 | major_, _ := strconv.Atoi(major)
56 | patch_, _ := strconv.Atoi(patch)
57 |
58 | var minor_ int
59 | switch modifier {
60 | case "alpha":
61 | minor_ = -2
62 | case "beta":
63 | minor_ = -1
64 | }
65 |
66 | return &Version{
67 | Major: major_,
68 | Minor: minor_,
69 | Patch: patch_,
70 | }
71 | } else {
72 | return nil
73 | }
74 | }
75 |
76 | func (self *Version) IsNewer(older *Version) bool {
77 | if self.Major > older.Major {
78 | return true
79 | } else if self.Major == older.Major {
80 | if self.Minor > older.Minor {
81 | return true
82 | } else if self.Minor == older.Minor {
83 | return self.Patch > older.Patch
84 | } else {
85 | return false
86 | }
87 | } else {
88 | return false
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/templates/NOTES.txt:
--------------------------------------------------------------------------------
1 | 1. Get the application URL by running these commands:
2 | {{- if .Values.ingress.enabled }}
3 | {{- range $host := .Values.ingress.hosts }}
4 | {{- range .paths }}
5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
6 | {{- end }}
7 | {{- end }}
8 | {{- else if contains "NodePort" .Values.service.type }}
9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "hello-world.fullname" . }})
10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
11 | echo http://$NODE_IP:$NODE_PORT
12 | {{- else if contains "LoadBalancer" .Values.service.type }}
13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available.
14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "hello-world.fullname" . }}'
15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "hello-world.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
16 | echo http://$SERVICE_IP:{{ .Values.service.port }}
17 | {{- else if contains "ClusterIP" .Values.service.type }}
18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "hello-world.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
20 | echo "Visit http://127.0.0.1:8080 to use your application"
21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
22 | {{- end }}
23 |
--------------------------------------------------------------------------------
/controller/delegation.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 |
7 | kubernetesutil "github.com/tliron/kutil/kubernetes"
8 | reposurepkg "github.com/tliron/reposure/apis/clientset/versioned"
9 | turandotpkg "github.com/tliron/turandot/apis/clientset/versioned"
10 | clientpkg "github.com/tliron/turandot/client"
11 | apiextensionspkg "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
12 | kubernetespkg "k8s.io/client-go/kubernetes"
13 | )
14 |
15 | func (self *Controller) NewDelegate(name string) (*clientpkg.Client, error) {
16 | configPath := filepath.Join(self.CachePath, "delegates", fmt.Sprintf("%s.yaml", name))
17 | if config, err := kubernetesutil.NewConfig(configPath, ""); err == nil {
18 | namespace, _ := kubernetesutil.GetConfiguredNamespace(configPath, "")
19 |
20 | var kubernetes *kubernetespkg.Clientset
21 | kubernetes, err := kubernetespkg.NewForConfig(config)
22 | if err != nil {
23 | return nil, err
24 | }
25 |
26 | var apiExtensions *apiextensionspkg.Clientset
27 | apiExtensions, err = apiextensionspkg.NewForConfig(config)
28 | if err != nil {
29 | return nil, err
30 | }
31 |
32 | var turandot *turandotpkg.Clientset
33 | turandot, err = turandotpkg.NewForConfig(config)
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | var reposure *reposurepkg.Clientset
39 | reposure, err = reposurepkg.NewForConfig(config)
40 | if err != nil {
41 | return nil, err
42 | }
43 |
44 | rest := kubernetes.CoreV1().RESTClient()
45 |
46 | return clientpkg.NewClient(
47 | kubernetes,
48 | apiExtensions,
49 | turandot,
50 | reposure,
51 | rest,
52 | config,
53 | self.Context,
54 | false,
55 | "",
56 | namespace,
57 | NamePrefix,
58 | PartOf,
59 | ManagedBy,
60 | OperatorImageName,
61 | CacheDirectory,
62 | fmt.Sprintf("turandot.client.%s", name),
63 | ), nil
64 | } else {
65 | return nil, err
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/kubernetes/1.0/interfaces.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | imports:
4 |
5 | - data.yaml
6 | - namespace_prefix: o11n
7 | file: ../../orchestration/1.0/profile.yaml
8 |
9 | interface_types:
10 |
11 | Command:
12 | derived_from: o11n:Execution
13 | description: >-
14 | Base type for Kubernetes command executions.
15 |
16 | The implementation is the command to execute.
17 | inputs:
18 | command:
19 | description: >-
20 | Command plus its arguments.
21 | type: list
22 | entry_schema: string
23 | constraints:
24 | - min_length: 1
25 | artifacts:
26 | description: >-
27 | A list of artifact names to copy to the target before execution.
28 |
29 | The artifact's "deploy_path" *must* be set. Directories will be created if they do not
30 | exist (using `mkdir --parents`).
31 |
32 | Note that there is special support for the "permissions" property of Deployable type
33 | artifacts.
34 | type: list
35 | entry_schema: string
36 | required: false
37 |
38 | ContainerCommand:
39 | description: >-
40 | Uses the SPDY executor on the "exec" subresource of the pods.
41 | derived_from: Command
42 | inputs:
43 | selector:
44 | type: LabelSelector
45 | required: false
46 | pods:
47 | type: ExecutionPods
48 | default: all
49 | container:
50 | description: >-
51 | The name of the container in the pods in which to execute.
52 |
53 | This is only required if the pods have more than one container.
54 | type: string
55 | required: false
56 |
57 | SSHCommand:
58 | description: >-
59 | Uses SSH to execute commands.
60 | derived_from: Command
61 | inputs:
62 | host:
63 | type: string
64 | username:
65 | type: string
66 | key:
67 | type: string
68 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/*
2 | Expand the name of the chart.
3 | */}}
4 | {{- define "hello-world.name" -}}
5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
6 | {{- end }}
7 |
8 | {{/*
9 | Create a default fully qualified app name.
10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
11 | If release name contains chart name it will be used as a full name.
12 | */}}
13 | {{- define "hello-world.fullname" -}}
14 | {{- if .Values.fullnameOverride }}
15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
16 | {{- else }}
17 | {{- $name := default .Chart.Name .Values.nameOverride }}
18 | {{- if contains $name .Release.Name }}
19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }}
20 | {{- else }}
21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
22 | {{- end }}
23 | {{- end }}
24 | {{- end }}
25 |
26 | {{/*
27 | Create chart name and version as used by the chart label.
28 | */}}
29 | {{- define "hello-world.chart" -}}
30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
31 | {{- end }}
32 |
33 | {{/*
34 | Common labels
35 | */}}
36 | {{- define "hello-world.labels" -}}
37 | helm.sh/chart: {{ include "hello-world.chart" . }}
38 | {{ include "hello-world.selectorLabels" . }}
39 | {{- if .Chart.AppVersion }}
40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
41 | {{- end }}
42 | app.kubernetes.io/managed-by: {{ .Release.Service }}
43 | {{- end }}
44 |
45 | {{/*
46 | Selector labels
47 | */}}
48 | {{- define "hello-world.selectorLabels" -}}
49 | app.kubernetes.io/name: {{ include "hello-world.name" . }}
50 | app.kubernetes.io/instance: {{ .Release.Name }}
51 | {{- end }}
52 |
53 | {{/*
54 | Create the name of the service account to use
55 | */}}
56 | {{- define "hello-world.serviceAccountName" -}}
57 | {{- if .Values.serviceAccount.create }}
58 | {{- default (include "hello-world.fullname" .) .Values.serviceAccount.name }}
59 | {{- else }}
60 | {{- default "default" .Values.serviceAccount.name }}
61 | {{- end }}
62 | {{- end }}
63 |
--------------------------------------------------------------------------------
/scripts/save-portable-container-image:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # Requirements (Fedora):
5 | # sudo dnf install podman jq pigz
6 |
7 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
8 | . "$HERE/_env"
9 | . "$HERE/_trap"
10 |
11 | if (( "$#" < 2 )); then
12 | echo 'usage: save-portable-container-image [IMAGE NAME] [FILE PATH]'
13 | exit 1
14 | fi
15 |
16 | if command -v podman &> /dev/null; then
17 | CONTAINER_IMAGE_TOOL=podman
18 | else
19 | CONTAINER_IMAGE_TOOL=docker
20 | fi
21 |
22 | if ! command -v "$CONTAINER_IMAGE_TOOL" &> /dev/null; then
23 | m 'missing container image tool: `podman` or `docker`' "$RED"
24 | MISSING=true
25 | fi
26 |
27 | if command -v pigz &> /dev/null; then
28 | GZIP_TOOL=pigz
29 | else
30 | GZIP_TOOL=gzip
31 | fi
32 |
33 | if ! command -v "$GZIP_TOOL" &> /dev/null; then
34 | m 'missing gzip tool: `pigz` or `gzip`' "$RED"
35 | MISSING=true
36 | fi
37 |
38 | if ! command -v tar &> /dev/null; then
39 | m 'missing `tar` tool' "$RED"
40 | MISSING=true
41 | fi
42 |
43 | if ! command -v jq &> /dev/null; then
44 | m 'missing `jq` tool' "$RED"
45 | MISSING=true
46 | fi
47 |
48 | if [ "$MISSING" == true ]; then
49 | exit 1
50 | fi
51 |
52 | IMAGE=$1
53 | FILE=$2
54 |
55 | EXTENSION=${FILE#*.}
56 | if [ "$EXTENSION" != tar.gz ]; then
57 | m 'file extension must be tar.gz' "$RED"
58 | exit 1
59 | fi
60 |
61 | WORK=$(mktemp --directory)
62 |
63 | copy_function goodbye old_goodbye
64 | function goodbye () {
65 | rm --recursive --force "$WORK"
66 | old_goodbye $1
67 | }
68 |
69 | # Save
70 | m "saving $IMAGE..."
71 | "$CONTAINER_IMAGE_TOOL" save "$IMAGE" --output "$WORK/image.tar"
72 |
73 | # Unpack
74 | m "unpacking..."
75 | mkdir --parents "$WORK/image"
76 | tar --extract --file="$WORK/image.tar" --directory="$WORK/image"
77 |
78 | # Fix
79 | cat "$WORK/image/manifest.json" | jq '.[].RepoTags += ["portable"]' > "$WORK/image/manifest.json~"
80 | mv --force "$WORK/image/manifest.json~" "$WORK/image/manifest.json"
81 |
82 | # Repack
83 | m "repacking to $FILE..."
84 | pushd "$WORK/image" > /dev/null
85 | tar --create --use-compress-program="$GZIP_TOOL" --file="$FILE" *
86 | popd > /dev/null
87 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/templates/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: {{ include "hello-world.fullname" . }}
5 | labels:
6 | {{- include "hello-world.labels" . | nindent 4 }}
7 | spec:
8 | {{- if not .Values.autoscaling.enabled }}
9 | replicas: {{ .Values.replicaCount }}
10 | {{- end }}
11 | selector:
12 | matchLabels:
13 | {{- include "hello-world.selectorLabels" . | nindent 6 }}
14 | template:
15 | metadata:
16 | {{- with .Values.podAnnotations }}
17 | annotations:
18 | {{- toYaml . | nindent 8 }}
19 | {{- end }}
20 | labels:
21 | {{- include "hello-world.selectorLabels" . | nindent 8 }}
22 | spec:
23 | {{- with .Values.imagePullSecrets }}
24 | imagePullSecrets:
25 | {{- toYaml . | nindent 8 }}
26 | {{- end }}
27 | serviceAccountName: {{ include "hello-world.serviceAccountName" . }}
28 | securityContext:
29 | {{- toYaml .Values.podSecurityContext | nindent 8 }}
30 | containers:
31 | - name: {{ .Chart.Name }}
32 | securityContext:
33 | {{- toYaml .Values.securityContext | nindent 12 }}
34 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
35 | imagePullPolicy: {{ .Values.image.pullPolicy }}
36 | ports:
37 | - name: http
38 | containerPort: 8080
39 | protocol: TCP
40 | livenessProbe:
41 | httpGet:
42 | path: /
43 | port: http
44 | readinessProbe:
45 | httpGet:
46 | path: /
47 | port: http
48 | resources:
49 | {{- toYaml .Values.resources | nindent 12 }}
50 | {{- with .Values.nodeSelector }}
51 | nodeSelector:
52 | {{- toYaml . | nindent 8 }}
53 | {{- end }}
54 | {{- with .Values.affinity }}
55 | affinity:
56 | {{- toYaml . | nindent 8 }}
57 | {{- end }}
58 | {{- with .Values.tolerations }}
59 | tolerations:
60 | {{- toYaml . | nindent 8 }}
61 | {{- end }}
62 |
--------------------------------------------------------------------------------
/turandot/commands/template-delist.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/tliron/kutil/util"
6 | )
7 |
8 | func init() {
9 | templateCommand.AddCommand(templateDelistCommand)
10 | templateDelistCommand.Flags().StringVarP(®istry, "registry", "r", "default", "name of registry")
11 | templateDelistCommand.Flags().BoolVarP(&all, "all", "a", false, "delist all templates")
12 | }
13 |
14 | var templateDelistCommand = &cobra.Command{
15 | Use: "delist [[SERVICE TEMPLATE NAME]]",
16 | Short: "Delist a service template from a registry (or all service templates)",
17 | Args: cobra.RangeArgs(0, 1),
18 | Run: func(cmd *cobra.Command, args []string) {
19 | if len(args) == 1 {
20 | serviceTemplateName := args[0]
21 | DelistServiceTemplate(serviceTemplateName)
22 | } else if all {
23 | DelistAllTemplates()
24 | } else {
25 | util.Fail("must provide service template name or specify \"--all\"")
26 | }
27 | },
28 | }
29 |
30 | func DelistServiceTemplate(serviceTemplateName string) {
31 | turandot := NewClient().Turandot()
32 | registry_, err := turandot.Reposure.RegistryClient().Get(namespace, registry)
33 | util.FailOnError(err)
34 | spooler := turandot.Reposure.SurrogateSpoolerClient(registry_)
35 |
36 | imageName := turandot.RegistryImageNameForServiceTemplateName(serviceTemplateName)
37 | err = spooler.DeleteImage(imageName)
38 | util.FailOnError(err)
39 | }
40 |
41 | func DelistAllTemplates() {
42 | turandot := NewClient().Turandot()
43 | registry_, err := turandot.Reposure.RegistryClient().Get(namespace, registry)
44 | util.FailOnError(err)
45 | command, err := turandot.Reposure.SurrogateCommandClient(registry_)
46 | util.FailOnError(err)
47 | imageNames, err := command.ListImages()
48 | util.FailOnError(err)
49 | spooler := turandot.Reposure.SurrogateSpoolerClient(registry_)
50 |
51 | for _, imageName := range imageNames {
52 | if serviceTemplateName, ok := turandot.ServiceTemplateNameForRegistryImageName(imageName); ok {
53 | log.Infof("deleting template: %s", serviceTemplateName)
54 | err := spooler.DeleteImage(imageName)
55 | util.FailOnError(err)
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/examples/helm/hello-world/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for hello-world.
2 | # This is a YAML-formatted file.
3 | # Declare variables to be passed into your templates.
4 |
5 | replicaCount: 1
6 |
7 | image:
8 | repository: bitnami/nginx
9 | pullPolicy: IfNotPresent
10 | # Overrides the image tag whose default is the chart appVersion.
11 | tag: ""
12 |
13 | imagePullSecrets: []
14 | nameOverride: ""
15 | fullnameOverride: ""
16 |
17 | serviceAccount:
18 | # Specifies whether a service account should be created
19 | create: true
20 | # Annotations to add to the service account
21 | annotations: {}
22 | # The name of the service account to use.
23 | # If not set and create is true, a name is generated using the fullname template
24 | name: ""
25 |
26 | podAnnotations: {}
27 |
28 | podSecurityContext: {}
29 | # fsGroup: 2000
30 |
31 | securityContext: {}
32 | # capabilities:
33 | # drop:
34 | # - ALL
35 | # readOnlyRootFilesystem: true
36 | # runAsNonRoot: true
37 | # runAsUser: 1000
38 |
39 | service:
40 | type: ClusterIP
41 | port: 80
42 |
43 | ingress:
44 | enabled: false
45 | annotations: {}
46 | # kubernetes.io/ingress.class: nginx
47 | # kubernetes.io/tls-acme: "true"
48 | hosts:
49 | - host: chart-example.local
50 | paths: []
51 | tls: []
52 | # - secretName: chart-example-tls
53 | # hosts:
54 | # - chart-example.local
55 |
56 | resources: {}
57 | # We usually recommend not to specify default resources and to leave this as a conscious
58 | # choice for the user. This also increases chances charts run on environments with little
59 | # resources, such as Minikube. If you do want to specify resources, uncomment the following
60 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
61 | # limits:
62 | # cpu: 100m
63 | # memory: 128Mi
64 | # requests:
65 | # cpu: 100m
66 | # memory: 128Mi
67 |
68 | autoscaling:
69 | enabled: false
70 | minReplicas: 1
71 | maxReplicas: 100
72 | targetCPUUtilizationPercentage: 80
73 | # targetMemoryUtilizationPercentage: 80
74 |
75 | nodeSelector: {}
76 |
77 | tolerations: []
78 |
79 | affinity: {}
80 |
--------------------------------------------------------------------------------
/apis/applyconfiguration/turandot.puccini.cloud/v1alpha1/servicetemplateindirect.go:
--------------------------------------------------------------------------------
1 | // Code generated by applyconfiguration-gen. DO NOT EDIT.
2 |
3 | package v1alpha1
4 |
5 | // ServiceTemplateIndirectApplyConfiguration represents an declarative configuration of the ServiceTemplateIndirect type for use
6 | // with apply.
7 | type ServiceTemplateIndirectApplyConfiguration struct {
8 | Namespace *string `json:"namespace,omitempty"`
9 | Registry *string `json:"registry,omitempty"`
10 | Name *string `json:"name,omitempty"`
11 | }
12 |
13 | // ServiceTemplateIndirectApplyConfiguration constructs an declarative configuration of the ServiceTemplateIndirect type for use with
14 | // apply.
15 | func ServiceTemplateIndirect() *ServiceTemplateIndirectApplyConfiguration {
16 | return &ServiceTemplateIndirectApplyConfiguration{}
17 | }
18 |
19 | // WithNamespace sets the Namespace field in the declarative configuration to the given value
20 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
21 | // If called multiple times, the Namespace field is set to the value of the last call.
22 | func (b *ServiceTemplateIndirectApplyConfiguration) WithNamespace(value string) *ServiceTemplateIndirectApplyConfiguration {
23 | b.Namespace = &value
24 | return b
25 | }
26 |
27 | // WithRegistry sets the Registry field in the declarative configuration to the given value
28 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
29 | // If called multiple times, the Registry field is set to the value of the last call.
30 | func (b *ServiceTemplateIndirectApplyConfiguration) WithRegistry(value string) *ServiceTemplateIndirectApplyConfiguration {
31 | b.Registry = &value
32 | return b
33 | }
34 |
35 | // WithName sets the Name field in the declarative configuration to the given value
36 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
37 | // If called multiple times, the Name field is set to the value of the last call.
38 | func (b *ServiceTemplateIndirectApplyConfiguration) WithName(value string) *ServiceTemplateIndirectApplyConfiguration {
39 | b.Name = &value
40 | return b
41 | }
42 |
--------------------------------------------------------------------------------
/turandot-profile-generator/parse.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/go-openapi/spec"
7 | )
8 |
9 | const extensionName = "x-kubernetes-group-version-kind"
10 | const defaultGroupPrefix = "k8s.io.api."
11 | const coreGroup = "core"
12 |
13 | func (self *Generator) GetTypeName(schema spec.Schema) string {
14 | name, group, _, kind := getGVK(schema)
15 | if rename, ok := self.Configuration.Rename[name]; ok {
16 | return rename
17 | } else if import_, ok := self.Configuration.Groups.Imports[group]; ok {
18 | return import_.NamespacePrefix + ":" + kind
19 | } else if kind != "" {
20 | return kind
21 | } else {
22 | return name
23 | }
24 | }
25 |
26 | func parseExtension(extension any) (string, string, string) {
27 | extension_ := extension.([]any)
28 | map_ := extension_[0].(map[string]any)
29 | group := fixGroup(map_["group"].(string))
30 | version := map_["version"].(string)
31 | kind := map_["kind"].(string)
32 | return group, version, kind
33 | }
34 |
35 | func parseGVK(name string) (string, string, string) {
36 | split := strings.Split(name, ".")
37 | length := len(split)
38 | if length < 2 {
39 | return "", "", ""
40 | }
41 | group := fixGroup(strings.Join(split[0:length-2], "."))
42 | version := split[length-2]
43 | kind := split[length-1]
44 | return group, version, kind
45 | }
46 |
47 | func getGVK(schema spec.Schema) (string, string, string, string) {
48 | if len(schema.Type) > 0 {
49 | return schema.Type[0], "", "", ""
50 | } else {
51 | ref := schema.Ref.GetPointer().String() // example: #/definitions/k8s.io.api.core.v1.LocalObjectReference
52 | split := strings.Split(ref, "/")
53 | name := split[len(split)-1]
54 | group, version, kind := parseGVK(name)
55 | return name, group, version, kind
56 | }
57 | }
58 |
59 | func isGVK(schema spec.Schema, group string, version string, kind string) bool {
60 | _, group_, version_, kind_ := getGVK(schema)
61 | return (group_ == group) && (version_ == version) && (kind_ == kind)
62 | }
63 |
64 | func fixGroup(group string) string {
65 | // In the main Kubernetes API the standard prefix is reversed for some reason
66 | if strings.HasPrefix(group, "io.k8s.") {
67 | group = "k8s.io." + group[len("io.k8s."):]
68 | }
69 | return group
70 | }
71 |
--------------------------------------------------------------------------------
/apis/applyconfiguration/turandot.puccini.cloud/v1alpha1/servicenodemodestate.go:
--------------------------------------------------------------------------------
1 | // Code generated by applyconfiguration-gen. DO NOT EDIT.
2 |
3 | package v1alpha1
4 |
5 | import (
6 | v1alpha1 "github.com/tliron/turandot/resources/turandot.puccini.cloud/v1alpha1"
7 | )
8 |
9 | // ServiceNodeModeStateApplyConfiguration represents an declarative configuration of the ServiceNodeModeState type for use
10 | // with apply.
11 | type ServiceNodeModeStateApplyConfiguration struct {
12 | Mode *string `json:"mode,omitempty"`
13 | State *v1alpha1.ModeState `json:"state,omitempty"`
14 | Message *string `json:"message,omitempty"`
15 | }
16 |
17 | // ServiceNodeModeStateApplyConfiguration constructs an declarative configuration of the ServiceNodeModeState type for use with
18 | // apply.
19 | func ServiceNodeModeState() *ServiceNodeModeStateApplyConfiguration {
20 | return &ServiceNodeModeStateApplyConfiguration{}
21 | }
22 |
23 | // WithMode sets the Mode field in the declarative configuration to the given value
24 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
25 | // If called multiple times, the Mode field is set to the value of the last call.
26 | func (b *ServiceNodeModeStateApplyConfiguration) WithMode(value string) *ServiceNodeModeStateApplyConfiguration {
27 | b.Mode = &value
28 | return b
29 | }
30 |
31 | // WithState sets the State field in the declarative configuration to the given value
32 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
33 | // If called multiple times, the State field is set to the value of the last call.
34 | func (b *ServiceNodeModeStateApplyConfiguration) WithState(value v1alpha1.ModeState) *ServiceNodeModeStateApplyConfiguration {
35 | b.State = &value
36 | return b
37 | }
38 |
39 | // WithMessage sets the Message field in the declarative configuration to the given value
40 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
41 | // If called multiple times, the Message field is set to the value of the last call.
42 | func (b *ServiceNodeModeStateApplyConfiguration) WithMessage(value string) *ServiceNodeModeStateApplyConfiguration {
43 | b.Message = &value
44 | return b
45 | }
46 |
--------------------------------------------------------------------------------
/controller/substitution.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | contextpkg "context"
5 |
6 | "github.com/tliron/exturl"
7 | reposure "github.com/tliron/reposure/resources/reposure.puccini.cloud/v1alpha1"
8 | )
9 |
10 | func (self *Controller) Substitute(context contextpkg.Context, namespace string, nodeTemplateName string, inputs map[string]any, mode string, site string, urlContext *exturl.Context) error {
11 | // hacky ;)
12 | registryName := "default"
13 | var serviceTemplateName string
14 | switch nodeTemplateName {
15 | case "central-pbx":
16 | serviceTemplateName = "asterisk-vnf"
17 | case "edge-pbx":
18 | serviceTemplateName = "asterisk-cnf"
19 | case "data-plane":
20 | serviceTemplateName = "simple-data-plane"
21 | }
22 | serviceName := serviceTemplateName
23 |
24 | if (site == "") || (site == self.Site) {
25 | // Local
26 | if registry, err := self.Client.Reposure.RegistryClient().Get(namespace, registryName); err == nil {
27 | if _, err := self.Client.CreateServiceFromTemplate(namespace, serviceName, registry, serviceTemplateName, inputs, mode); err != nil {
28 | return err
29 | }
30 | } else {
31 | return err
32 | }
33 | } else {
34 | // Delegate
35 | self.Log.Infof("delegating %q to: %s", serviceTemplateName, site)
36 | if remoteClient, err := self.NewDelegate(site); err == nil {
37 | if err := remoteClient.InstallOperator(site, "docker.io", true); err != nil {
38 | return err
39 | }
40 | if err := remoteClient.Reposure.InstallOperator("docker.io", true); err != nil {
41 | return err
42 | }
43 |
44 | // hack: Minikube
45 | var remoteRegistry *reposure.Registry
46 | if remoteRegistry, err = remoteClient.Reposure.CreateRegistryIndirect(namespace, "default", "kube-system", "registry", 80, "", "", ""); err != nil {
47 | return err
48 | }
49 |
50 | if url, err := remoteClient.GetRegistryServiceTemplateURL(remoteRegistry, serviceTemplateName); err == nil {
51 | if url_, err := urlContext.NewURL(url); err == nil {
52 | if _, err := remoteClient.CreateServiceFromContent(context, namespace, serviceName, remoteRegistry, url_, inputs, mode); err != nil {
53 | return err
54 | }
55 | } else {
56 | return err
57 | }
58 | } else {
59 | return err
60 | }
61 | } else {
62 | return err
63 | }
64 | }
65 |
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/apis/applyconfiguration/turandot.puccini.cloud/v1alpha1/servicespec.go:
--------------------------------------------------------------------------------
1 | // Code generated by applyconfiguration-gen. DO NOT EDIT.
2 |
3 | package v1alpha1
4 |
5 | // ServiceSpecApplyConfiguration represents an declarative configuration of the ServiceSpec type for use
6 | // with apply.
7 | type ServiceSpecApplyConfiguration struct {
8 | ServiceTemplate *ServiceTemplateApplyConfiguration `json:"serviceTemplate,omitempty"`
9 | Inputs map[string]string `json:"inputs,omitempty"`
10 | Mode *string `json:"mode,omitempty"`
11 | }
12 |
13 | // ServiceSpecApplyConfiguration constructs an declarative configuration of the ServiceSpec type for use with
14 | // apply.
15 | func ServiceSpec() *ServiceSpecApplyConfiguration {
16 | return &ServiceSpecApplyConfiguration{}
17 | }
18 |
19 | // WithServiceTemplate sets the ServiceTemplate field in the declarative configuration to the given value
20 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
21 | // If called multiple times, the ServiceTemplate field is set to the value of the last call.
22 | func (b *ServiceSpecApplyConfiguration) WithServiceTemplate(value *ServiceTemplateApplyConfiguration) *ServiceSpecApplyConfiguration {
23 | b.ServiceTemplate = value
24 | return b
25 | }
26 |
27 | // WithInputs puts the entries into the Inputs field in the declarative configuration
28 | // and returns the receiver, so that objects can be build by chaining "With" function invocations.
29 | // If called multiple times, the entries provided by each call will be put on the Inputs field,
30 | // overwriting an existing map entries in Inputs field with the same key.
31 | func (b *ServiceSpecApplyConfiguration) WithInputs(entries map[string]string) *ServiceSpecApplyConfiguration {
32 | if b.Inputs == nil && len(entries) > 0 {
33 | b.Inputs = make(map[string]string, len(entries))
34 | }
35 | for k, v := range entries {
36 | b.Inputs[k] = v
37 | }
38 | return b
39 | }
40 |
41 | // WithMode sets the Mode field in the declarative configuration to the given value
42 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
43 | // If called multiple times, the Mode field is set to the value of the last call.
44 | func (b *ServiceSpecApplyConfiguration) WithMode(value string) *ServiceSpecApplyConfiguration {
45 | b.Mode = &value
46 | return b
47 | }
48 |
--------------------------------------------------------------------------------
/controller/artifact.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | contextpkg "context"
5 | "fmt"
6 |
7 | "github.com/tliron/exturl"
8 | "github.com/tliron/turandot/controller/parser"
9 | resources "github.com/tliron/turandot/resources/turandot.puccini.cloud/v1alpha1"
10 | )
11 |
12 | func (self *Controller) publishArtifactsToRegistry(context contextpkg.Context, artifacts parser.KubernetesArtifacts, service *resources.Service, urlContext *exturl.Context) (map[string]string, error) {
13 | if len(artifacts) > 0 {
14 | artifactMappings := make(map[string]string)
15 |
16 | for _, artifact := range artifacts {
17 | if registry, err := self.Client.Reposure.RegistryClient().Get(service.Namespace, artifact.Registry); err == nil {
18 | if err := self.Client.Reposure.RegistryClient().UpdateURLContext(registry, urlContext); err == nil {
19 | // Note: OpenShift registry permissions require the namespace as the repository
20 | imageName := fmt.Sprintf("%s/%s", service.Namespace, artifact.Name)
21 |
22 | if directClient, err := self.Client.Reposure.DirectClient(registry); err == nil {
23 | if url, err := urlContext.NewURL(artifact.SourcePath); err == nil {
24 | self.Log.Infof("publishing image %q at %q on %q", imageName, url.String(), directClient.Host)
25 | if name, err := directClient.PushGzippedTarballFromURL(context, url, imageName); err == nil {
26 | self.Log.Infof("published image %q at %q on %q", imageName, url.String(), directClient.Host)
27 | artifactMappings[artifact.SourcePath] = name
28 | } else {
29 | return nil, err
30 | }
31 | } else {
32 | return nil, err
33 | }
34 | } else {
35 | return nil, err
36 | }
37 | } else {
38 | return nil, err
39 | }
40 | } else {
41 | return nil, err
42 | }
43 | }
44 |
45 | /*
46 | if ips, err := kubernetes.GetServiceIPs(self.Context, self.Kubernetes, service.Namespace, "turandot-registry"); err == nil {
47 | for _, artifact := range artifacts {
48 | if name, err := self.PublishOnRegistry(artifact.Name, artifact.SourcePath, ips, urlContext); err == nil {
49 | artifactMappings[artifact.SourcePath] = name
50 | } else {
51 | return nil, err
52 | }
53 | }
54 | }
55 | */
56 |
57 | if len(artifactMappings) == 0 {
58 | return nil, nil
59 | } else {
60 | return artifactMappings, nil
61 | }
62 | } else {
63 | return nil, nil
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/turandot/commands/client.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | contextpkg "context"
5 | "fmt"
6 |
7 | kubernetesutil "github.com/tliron/kutil/kubernetes"
8 | "github.com/tliron/kutil/util"
9 | reposurepkg "github.com/tliron/reposure/apis/clientset/versioned"
10 | turandotpkg "github.com/tliron/turandot/apis/clientset/versioned"
11 | clientpkg "github.com/tliron/turandot/client"
12 | "github.com/tliron/turandot/controller"
13 | apiextensionspkg "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
14 | kubernetespkg "k8s.io/client-go/kubernetes"
15 | restpkg "k8s.io/client-go/rest"
16 | )
17 |
18 | //
19 | // Client
20 | //
21 |
22 | type Client struct {
23 | Config *restpkg.Config
24 | Kubernetes kubernetespkg.Interface
25 | REST restpkg.Interface
26 | Context contextpkg.Context
27 | Namespace string
28 | }
29 |
30 | func NewClient() *Client {
31 | config, err := kubernetesutil.NewConfigFromFlags(masterUrl, kubeconfigPath, kubeconfigContext, log)
32 | util.FailOnError(err)
33 |
34 | kubernetes, err := kubernetespkg.NewForConfig(config)
35 | util.FailOnError(err)
36 |
37 | namespace_ := namespace
38 | if clusterMode {
39 | namespace_ = ""
40 | } else if namespace_ == "" {
41 | if namespace__, ok := kubernetesutil.GetConfiguredNamespace(kubeconfigPath, kubeconfigContext); ok {
42 | namespace_ = namespace__
43 | }
44 | if namespace_ == "" {
45 | util.Fail("could not discover namespace and \"--namespace\" not provided")
46 | }
47 | }
48 |
49 | return &Client{
50 | Config: config,
51 | Kubernetes: kubernetes,
52 | REST: kubernetes.CoreV1().RESTClient(),
53 | Context: context,
54 | Namespace: namespace_,
55 | }
56 | }
57 |
58 | func (self *Client) Turandot() *clientpkg.Client {
59 | apiExtensions, err := apiextensionspkg.NewForConfig(self.Config)
60 | util.FailOnError(err)
61 |
62 | turandot, err := turandotpkg.NewForConfig(self.Config)
63 | util.FailOnError(err)
64 |
65 | reposure, err := reposurepkg.NewForConfig(self.Config)
66 | util.FailOnError(err)
67 |
68 | return clientpkg.NewClient(
69 | self.Kubernetes,
70 | apiExtensions,
71 | turandot,
72 | reposure,
73 | self.REST,
74 | self.Config,
75 | self.Context,
76 | clusterMode,
77 | clusterRole,
78 | self.Namespace,
79 | controller.NamePrefix,
80 | controller.PartOf,
81 | controller.ManagedBy,
82 | controller.OperatorImageName,
83 | controller.CacheDirectory,
84 | fmt.Sprintf("%s.client", toolName),
85 | )
86 | }
87 |
--------------------------------------------------------------------------------
/turandot/commands/template-register.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | contextpkg "context"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/tliron/exturl"
8 | "github.com/tliron/kutil/util"
9 | )
10 |
11 | func init() {
12 | templateCommand.AddCommand(templateRegisterCommand)
13 | templateRegisterCommand.Flags().StringVarP(®istry, "registry", "r", "default", "name of registry")
14 | templateRegisterCommand.Flags().StringVarP(&filePath, "file", "f", "", "path to a local CSAR or TOSCA YAML file (will be uploaded)")
15 | templateRegisterCommand.Flags().StringVarP(&directoryPath, "directory", "d", "", "path to a local directory of TOSCA YAML files (will be packed into a CSAR and uploaded)")
16 | }
17 |
18 | var templateRegisterCommand = &cobra.Command{
19 | Use: "register [SERVICE TEMPLATE NAME]",
20 | Short: "Register a service template in a registry from CSAR or TOSCA YAML content",
21 | Args: cobra.ExactArgs(1),
22 | Run: func(cmd *cobra.Command, args []string) {
23 | serviceTemplateName := args[0]
24 | RegisterServiceTemplate(contextpkg.TODO(), serviceTemplateName)
25 | },
26 | }
27 |
28 | func RegisterServiceTemplate(context contextpkg.Context, serviceTemplateName string) {
29 | urlContext := exturl.NewContext()
30 | defer urlContext.Release()
31 |
32 | if filePath != "" {
33 | if directoryPath != "" {
34 | registerFailOnlyOneOf()
35 | }
36 |
37 | url, err := urlContext.NewValidFileURL(filePath)
38 | util.FailOnError(err)
39 | registerServiceTemplate(context, serviceTemplateName, url)
40 | } else if directoryPath != "" {
41 | if filePath != "" {
42 | registerFailOnlyOneOf()
43 | }
44 |
45 | // TODO pack directory into CSAR
46 | } else {
47 | url, err := urlContext.ReadToInternalURLFromStdin(context, "yaml")
48 | util.FailOnError(err)
49 | registerServiceTemplate(context, serviceTemplateName, url)
50 | }
51 | }
52 |
53 | func registerServiceTemplate(context contextpkg.Context, serviceTemplateName string, url exturl.URL) {
54 | turandot := NewClient().Turandot()
55 | registry_, err := turandot.Reposure.RegistryClient().Get(namespace, registry)
56 | util.FailOnError(err)
57 | spooler := turandot.Reposure.SurrogateSpoolerClient(registry_)
58 |
59 | imageName := turandot.RegistryImageNameForServiceTemplateName(serviceTemplateName)
60 | err = spooler.PushTarballFromURL(context, imageName, url)
61 | util.FailOnError(err)
62 | }
63 |
64 | func registerFailOnlyOneOf() {
65 | util.Fail("must provide only one of \"--file\" or \"--directory\"")
66 | }
67 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/simple-data-plane.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | metadata:
4 |
5 | template_name: Simple Data Plane
6 | template_author: Turandot
7 |
8 | imports:
9 |
10 | - namespace_prefix: ns
11 | file: profiles/network-service/profile.yaml
12 | - namespace_prefix: k8s
13 | file: profiles/kubernetes/1.0/profile.yaml
14 | - namespace_prefix: o11n
15 | file: profiles/orchestration/1.0/profile.yaml
16 |
17 | node_types:
18 |
19 | NetworkAttachmentDefinition:
20 | metadata:
21 | turandot.generateNames: 'false'
22 | capabilities:
23 | metadata: k8s:Metadata
24 | network-attachment-definition: k8s:BridgeNetworkAttachmentDefinition
25 |
26 | topology_template:
27 |
28 | inputs:
29 |
30 | namespace:
31 | type: string
32 | default: workspace
33 |
34 | name:
35 | type: string
36 | default: data-plane
37 |
38 | ip-prefix:
39 | type: string
40 | default: '192.168.2'
41 |
42 | node_templates:
43 |
44 | router:
45 | type: ns:Router
46 |
47 | bridge:
48 | type: NetworkAttachmentDefinition
49 | capabilities:
50 | metadata:
51 | properties:
52 | name: { get_input: name }
53 | namespace: { get_input: namespace }
54 | labels:
55 | app.kubernetes.io/name: { get_input: name }
56 | network-attachment-definition:
57 | properties:
58 | config:
59 | name: { get_input: name }
60 | bridge: { get_input: name }
61 | isDefaultGateway: true
62 | ipMasq: true
63 | promiscMode: true
64 | ipam:
65 | type: host-local
66 | subnet: { concat: [ { get_input: ip-prefix }, '.0/24' ] }
67 | rangeStart: { concat: [ { get_input: ip-prefix }, '.2' ] }
68 | rangeEnd: { concat: [ { get_input: ip-prefix }, '.254' ] }
69 | routes:
70 | - dst: '0.0.0.0/0'
71 | gateway: { concat: [ { get_input: ip-prefix }, '.1' ] }
72 |
73 | substitution_mappings:
74 |
75 | node_type: ns:NetworkPlane
76 | capabilities:
77 | connection: [ router, route ]
78 |
79 | policies:
80 |
81 | # Note: "router" will also inherit the provisioning policy from via substitution
82 | - wan:
83 | type: o11n:Provisioning
84 | properties:
85 | virtualizable: false
86 | targets:
87 | - router
88 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/orchestration/1.0/policies.yaml:
--------------------------------------------------------------------------------
1 | tosca_definitions_version: tosca_simple_yaml_1_3
2 |
3 | imports:
4 |
5 | - data.yaml
6 |
7 | policy_types:
8 |
9 | Provisioning:
10 | description: >-
11 | Direction for where and how nodes for a node template are provisioned.
12 |
13 | "Provisioning" refers to either instantiation (creating a new node) or allocation (using an
14 | existing node from an inventory).
15 |
16 | Provisioning policies are "inherited" via node template substitution such that they would
17 | be applied to all node templates within the substituted service template. That substituted
18 | service template may override the inherited policy for specific nodes.
19 | properties:
20 | sites:
21 | description: >-
22 | Nodes will be provisioned on all listed sites.
23 |
24 | Note that a site may have multiple clusters. One or more of them may be selected for
25 | provisioning.
26 | type: list
27 | entry_schema: string
28 | required: false
29 | profile:
30 | description: >-
31 | Multiple provisioning profiles may be available, e.g. multiple service templates for
32 | substitution, multiple physical resources for allocation, etc.
33 |
34 | Setting this property will provide hints for selecting a specific profile.
35 | type: ProvisioningProfile
36 | required: false
37 | substitutable:
38 | description: >-
39 | When true allows the node template to be substituted by another service template, which
40 | must have compatible subtitution mappings.
41 | type: boolean
42 | default: true
43 | instantiable:
44 | description: >-
45 | When true allows nodes to be instantiated. False means that only pre-existing nodes will
46 | be allocated.
47 | type: boolean
48 | default: true
49 | virtualizable:
50 | description: >-
51 | When true allows nodes to be virtual (virtual machines or containers). False means that
52 | only physical nodes (baremetal) can be used. When false the "instantiable" property is
53 | ignored, because physical nodes cannot be instantiated, only allocated.
54 | type: boolean
55 | default: true
56 | substitutionInputs:
57 | description: >-
58 | These values will be used as inputs for substitution.
59 | type: map
60 | entry_schema: string
61 | required: false
62 |
--------------------------------------------------------------------------------
/turandot/commands/template-list.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "sort"
5 | "strings"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/tliron/exturl"
9 | "github.com/tliron/go-ard"
10 | "github.com/tliron/kutil/terminal"
11 | "github.com/tliron/kutil/util"
12 | )
13 |
14 | func init() {
15 | templateCommand.AddCommand(templateListCommand)
16 | templateListCommand.Flags().StringVarP(®istry, "registry", "r", "default", "name of registry")
17 | }
18 |
19 | var templateListCommand = &cobra.Command{
20 | Use: "list",
21 | Short: "List service templates registered in a registry",
22 | Run: func(cmd *cobra.Command, args []string) {
23 | ListServiceTemplates()
24 | },
25 | }
26 |
27 | func ListServiceTemplates() {
28 | turandot := NewClient().Turandot()
29 | registry_, err := turandot.Reposure.RegistryClient().Get(namespace, registry)
30 | util.FailOnError(err)
31 | command, err := turandot.Reposure.SurrogateCommandClient(registry_)
32 | util.FailOnError(err)
33 | imageNames, err := command.ListImages()
34 | util.FailOnError(err)
35 |
36 | if len(imageNames) == 0 {
37 | return
38 | }
39 | sort.Strings(imageNames)
40 |
41 | switch format {
42 | case "":
43 | urlContext := exturl.NewContext()
44 | defer urlContext.Release()
45 |
46 | table := terminal.NewTable(maxWidth, "Name", "Services")
47 | for _, imageName := range imageNames {
48 | if serviceTemplateName, ok := turandot.ServiceTemplateNameForRegistryImageName(imageName); ok {
49 | services, err := turandot.ListServicesForImageName(registry, imageName, urlContext)
50 | util.FailOnError(err)
51 | sort.Strings(services)
52 | table.Add(serviceTemplateName, strings.Join(services, "\n"))
53 | }
54 | }
55 | table.Print()
56 |
57 | case "bare":
58 | for _, imageName := range imageNames {
59 | if serviceTemplateName, ok := turandot.ServiceTemplateNameForRegistryImageName(imageName); ok {
60 | terminal.Println(serviceTemplateName)
61 | }
62 | }
63 |
64 | default:
65 | urlContext := exturl.NewContext()
66 | defer urlContext.Release()
67 |
68 | list := make(ard.List, 0, len(imageNames))
69 | for _, imageName := range imageNames {
70 | if serviceTemplateName, ok := turandot.ServiceTemplateNameForRegistryImageName(imageName); ok {
71 | map_ := make(ard.StringMap)
72 | map_["Name"] = serviceTemplateName
73 | map_["Services"], err = turandot.ListServicesForImageName(registry, imageName, urlContext)
74 | util.FailOnError(err)
75 | list = append(list, map_)
76 | }
77 | }
78 | Transcriber().Write(list)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/turandot/commands/service-list.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/tliron/go-ard"
9 | "github.com/tliron/kutil/terminal"
10 | "github.com/tliron/kutil/util"
11 | resources "github.com/tliron/turandot/resources/turandot.puccini.cloud/v1alpha1"
12 | )
13 |
14 | func init() {
15 | serviceCommand.AddCommand(serviceListCommand)
16 | }
17 |
18 | var serviceListCommand = &cobra.Command{
19 | Use: "list",
20 | Short: "List deployed services",
21 | Run: func(cmd *cobra.Command, args []string) {
22 | ListServices()
23 | },
24 | }
25 |
26 | func ListServices() {
27 | services, err := NewClient().Turandot().ListServices()
28 | util.FailOnError(err)
29 | if len(services.Items) == 0 {
30 | return
31 | }
32 | // TODO: sort services by name? they seem already sorted!
33 |
34 | switch format {
35 | case "":
36 | table := terminal.NewTable(maxWidth, "Name", "State", "Mode", "Inputs", "Outputs")
37 | for _, service := range services.Items {
38 | mode := fmt.Sprintf("%s\n", service.Status.Mode)
39 | if service.Status.NodeStates != nil {
40 | for node, nodeState := range service.Status.NodeStates {
41 | if nodeState.Mode == service.Status.Mode {
42 | mode += fmt.Sprintf("%s: %s\n", node, nodeState.State)
43 | }
44 | }
45 | }
46 |
47 | var inputs string
48 | if service.Spec.Inputs != nil {
49 | for _, name := range sortedMapStringStringKeys(service.Spec.Inputs) {
50 | input := service.Spec.Inputs[name]
51 | inputs += fmt.Sprintf("%s: %s\n", name, input)
52 | }
53 | }
54 |
55 | var outputs string
56 | if service.Status.Outputs != nil {
57 | for _, name := range sortedMapStringStringKeys(service.Status.Outputs) {
58 | output := service.Status.Outputs[name]
59 | outputs += fmt.Sprintf("%s: %s\n", name, output)
60 | }
61 | }
62 |
63 | table.Add(service.Name, string(service.Status.InstantiationState), mode, inputs, outputs)
64 | }
65 | table.Print()
66 |
67 | case "bare":
68 | for _, service := range services.Items {
69 | terminal.Println(service.Name)
70 | }
71 |
72 | default:
73 | list := make(ard.List, len(services.Items))
74 | for index, service := range services.Items {
75 | list[index] = resources.ServiceToARD(&service)
76 | }
77 | Transcriber().Write(list)
78 | }
79 | }
80 |
81 | func sortedMapStringStringKeys(map_ map[string]string) []string {
82 | keys := make([]string, 0, len(map_))
83 | for key := range map_ {
84 | keys = append(keys, key)
85 | }
86 | sort.Strings(keys)
87 | return keys
88 | }
89 |
--------------------------------------------------------------------------------
/apis/clientset/versioned/fake/clientset_generated.go:
--------------------------------------------------------------------------------
1 | // Code generated by client-gen. DO NOT EDIT.
2 |
3 | package fake
4 |
5 | import (
6 | clientset "github.com/tliron/turandot/apis/clientset/versioned"
7 | turandotv1alpha1 "github.com/tliron/turandot/apis/clientset/versioned/typed/turandot.puccini.cloud/v1alpha1"
8 | faketurandotv1alpha1 "github.com/tliron/turandot/apis/clientset/versioned/typed/turandot.puccini.cloud/v1alpha1/fake"
9 | "k8s.io/apimachinery/pkg/runtime"
10 | "k8s.io/apimachinery/pkg/watch"
11 | "k8s.io/client-go/discovery"
12 | fakediscovery "k8s.io/client-go/discovery/fake"
13 | "k8s.io/client-go/testing"
14 | )
15 |
16 | // NewSimpleClientset returns a clientset that will respond with the provided objects.
17 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is,
18 | // without applying any validations and/or defaults. It shouldn't be considered a replacement
19 | // for a real clientset and is mostly useful in simple unit tests.
20 | func NewSimpleClientset(objects ...runtime.Object) *Clientset {
21 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
22 | for _, obj := range objects {
23 | if err := o.Add(obj); err != nil {
24 | panic(err)
25 | }
26 | }
27 |
28 | cs := &Clientset{tracker: o}
29 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
30 | cs.AddReactor("*", "*", testing.ObjectReaction(o))
31 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
32 | gvr := action.GetResource()
33 | ns := action.GetNamespace()
34 | watch, err := o.Watch(gvr, ns)
35 | if err != nil {
36 | return false, nil, err
37 | }
38 | return true, watch, nil
39 | })
40 |
41 | return cs
42 | }
43 |
44 | // Clientset implements clientset.Interface. Meant to be embedded into a
45 | // struct to get a default implementation. This makes faking out just the method
46 | // you want to test easier.
47 | type Clientset struct {
48 | testing.Fake
49 | discovery *fakediscovery.FakeDiscovery
50 | tracker testing.ObjectTracker
51 | }
52 |
53 | func (c *Clientset) Discovery() discovery.DiscoveryInterface {
54 | return c.discovery
55 | }
56 |
57 | func (c *Clientset) Tracker() testing.ObjectTracker {
58 | return c.tracker
59 | }
60 |
61 | var (
62 | _ clientset.Interface = &Clientset{}
63 | _ testing.FakeClient = &Clientset{}
64 | )
65 |
66 | // TurandotV1alpha1 retrieves the TurandotV1alpha1Client
67 | func (c *Clientset) TurandotV1alpha1() turandotv1alpha1.TurandotV1alpha1Interface {
68 | return &faketurandotv1alpha1.FakeTurandotV1alpha1{Fake: &c.Fake}
69 | }
70 |
--------------------------------------------------------------------------------
/controller/parser/kubernetes-resource-mapping.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "github.com/tliron/go-transcribe"
5 | "github.com/tliron/kutil/kubernetes"
6 | "github.com/tliron/kutil/util"
7 | "gopkg.in/yaml.v3"
8 | "k8s.io/apimachinery/pkg/runtime/schema"
9 | )
10 |
11 | //
12 | // KubernetesResourceMapping
13 | //
14 |
15 | type KubernetesResourceMapping struct {
16 | Capability string `yaml:"capability" json:"capability"`
17 | APIVersion string `yaml:"apiVersion" json:"apiVersion"`
18 | Kind string `yaml:"kind" json:"kind"`
19 | Name string `yaml:"name" json:"name"`
20 | Namespace string `yaml:"namespace" json:"namespace"`
21 | AttributeMappings map[string]string `yaml:"attributes,omitempty" json:"attributes,omitempty"`
22 | }
23 |
24 | //
25 | // KubernetesResourceMappingList
26 | //
27 |
28 | type KubernetesResourceMappingList []*KubernetesResourceMapping
29 |
30 | func (self *KubernetesResourceMapping) GVK() (schema.GroupVersionKind, error) {
31 | if gvk, err := kubernetes.ParseGVK(self.APIVersion, self.Kind); err == nil {
32 | return gvk, nil
33 | } else {
34 | return schema.GroupVersionKind{}, err
35 | }
36 | }
37 |
38 | //
39 | // KubernetesResourceMappings
40 | //
41 |
42 | type KubernetesResourceMappings map[string]KubernetesResourceMappingList
43 |
44 | func NewKubernetesResourceMappings() KubernetesResourceMappings {
45 | return make(KubernetesResourceMappings)
46 | }
47 |
48 | func DecodeKubernetesResourceMappings(code string) (KubernetesResourceMappings, bool) {
49 | var self KubernetesResourceMappings
50 | if err := yaml.Unmarshal(util.StringToBytes(code), &self); err == nil {
51 | return self, true
52 | } else {
53 | return nil, false
54 | }
55 | }
56 |
57 | func (self KubernetesResourceMappings) Add(vertexId string, capability string, apiVersion string, kind string, name string, namespace string, attributeMappings map[string]string) {
58 | mapping := &KubernetesResourceMapping{
59 | Capability: capability,
60 | APIVersion: apiVersion,
61 | Kind: kind,
62 | Name: name,
63 | Namespace: namespace,
64 | AttributeMappings: attributeMappings,
65 | }
66 | self[vertexId] = append(self[vertexId], mapping)
67 | }
68 |
69 | func (self KubernetesResourceMappings) JSON() map[string]string {
70 | map_ := make(map[string]string)
71 | for vertexId, list := range self {
72 | if value, err := transcribe.NewTranscriber().StringifyJSON(list); err == nil {
73 | map_[vertexId] = value
74 | }
75 | }
76 | return map_
77 | }
78 |
--------------------------------------------------------------------------------
/turandot-profile-generator/generator.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 |
8 | "github.com/go-openapi/loads"
9 | "gopkg.in/yaml.v2"
10 | )
11 |
12 | //
13 | // Generator
14 | //
15 |
16 | type Generator struct {
17 | ConfigurationPath string
18 | Configuration Configuration
19 | OpenAPI *loads.Document
20 | Nodes Nodes
21 | Writer *os.File
22 | }
23 |
24 | func NewGenerator(configurationPath string) (*Generator, error) {
25 | self := Generator{ConfigurationPath: configurationPath}
26 | if err := self.ReadConfig(); err == nil {
27 | if err = self.ReadOpenAPI(); err == nil {
28 | self.Nodes = self.ReadNodes()
29 | return &self, nil
30 | } else {
31 | return nil, err
32 | }
33 | } else {
34 | return nil, err
35 | }
36 | }
37 |
38 | func (self *Generator) OpenWriter(fileName string) error {
39 | var err error
40 | path := filepath.Join(filepath.Dir(self.ConfigurationPath), self.Configuration.OutputDir, fileName)
41 | if self.Writer, err = os.Create(path); err == nil {
42 | fmt.Println("writing", path)
43 | }
44 | return err
45 | }
46 |
47 | func (self *Generator) CloseWriter() error {
48 | if self.Writer != nil {
49 | return self.Writer.Close()
50 | } else {
51 | return nil
52 | }
53 | }
54 |
55 | func (self *Generator) Generate() error {
56 | capabilityNodes, dataNodes := self.SplitNodes()
57 |
58 | if err := self.GenerateFile("capabilities.yaml", "capability", capabilityNodes); err != nil {
59 | return err
60 | }
61 |
62 | if err := self.GenerateFile("data.yaml", "data", dataNodes); err != nil {
63 | return err
64 | }
65 |
66 | return nil
67 | }
68 |
69 | func (self *Generator) GenerateFile(fileName string, entity string, nodes Nodes) error {
70 | if err := self.OpenWriter(fileName); err != nil {
71 | return err
72 | }
73 | defer self.CloseWriter()
74 |
75 | self.WriteHeader(entity)
76 |
77 | self.Writeln()
78 | self.WriteKey(0, entity+"_types")
79 |
80 | isCapability := entity == "capability"
81 | for _, node := range nodes {
82 | self.Writeln()
83 | self.WriteType(node, isCapability)
84 | }
85 |
86 | return nil
87 | }
88 |
89 | func (self *Generator) ReadConfig() error {
90 | if bytes, err := os.ReadFile(self.ConfigurationPath); err == nil {
91 | return yaml.Unmarshal(bytes, &self.Configuration)
92 | } else {
93 | return err
94 | }
95 | }
96 |
97 | func (self *Generator) ReadOpenAPI() error {
98 | var err error
99 | self.OpenAPI, err = loads.JSONSpec(self.Configuration.OpenAPI)
100 | return err
101 | }
102 |
--------------------------------------------------------------------------------
/turandot-operator/controller.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/heptiolabs/healthcheck"
8 | "github.com/tliron/kutil/kubernetes"
9 | "github.com/tliron/kutil/util"
10 | versionpkg "github.com/tliron/kutil/version"
11 | reposurepkg "github.com/tliron/reposure/apis/clientset/versioned"
12 | turandotpkg "github.com/tliron/turandot/apis/clientset/versioned"
13 | controllerpkg "github.com/tliron/turandot/controller"
14 | apiextensionspkg "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
15 | "k8s.io/client-go/dynamic"
16 | kubernetespkg "k8s.io/client-go/kubernetes"
17 | "k8s.io/client-go/tools/clientcmd"
18 | )
19 |
20 | func Controller() {
21 | if version {
22 | versionpkg.Print()
23 | util.Exit(0)
24 | return
25 | }
26 |
27 | log.Noticef("%s version=%s revision=%s site=%s", toolName, versionpkg.GitVersion, versionpkg.GitRevision, site)
28 |
29 | // Config
30 |
31 | config, err := clientcmd.BuildConfigFromFlags(masterUrl, kubeconfigPath)
32 | util.FailOnError(err)
33 |
34 | if clusterMode {
35 | namespace = ""
36 | } else if namespace == "" {
37 | if namespace_, ok := kubernetes.GetConfiguredNamespace(kubeconfigPath, kubeconfigContext); ok {
38 | namespace = namespace_
39 | }
40 | if namespace == "" {
41 | namespace = kubernetes.GetServiceAccountNamespace()
42 | }
43 | if namespace == "" {
44 | util.Fail("could not discover namespace and namespace not provided")
45 | }
46 | }
47 |
48 | // Clients
49 |
50 | dynamicClient, err := dynamic.NewForConfig(config)
51 | util.FailOnError(err)
52 |
53 | kubernetesClient, err := kubernetespkg.NewForConfig(config)
54 | util.FailOnError(err)
55 |
56 | apiExtensionsClient, err := apiextensionspkg.NewForConfig(config)
57 | util.FailOnError(err)
58 |
59 | turandotClient, err := turandotpkg.NewForConfig(config)
60 | util.FailOnError(err)
61 |
62 | reposureClient, err := reposurepkg.NewForConfig(config)
63 | util.FailOnError(err)
64 |
65 | // Controller
66 |
67 | controller := controllerpkg.NewController(
68 | context,
69 | toolName,
70 | site,
71 | clusterMode,
72 | clusterRole,
73 | namespace,
74 | dynamicClient,
75 | kubernetesClient,
76 | apiExtensionsClient,
77 | turandotClient,
78 | reposureClient,
79 | config,
80 | cachePath,
81 | resyncPeriod,
82 | util.SetupSignalHandler(),
83 | )
84 |
85 | // Run
86 |
87 | err = controller.Run(concurrency, func() {
88 | log.Info("starting health monitor")
89 | health := healthcheck.NewHandler()
90 | err := http.ListenAndServe(fmt.Sprintf(":%d", healthPort), health)
91 | util.FailOnError(err)
92 | })
93 | util.FailOnError(err)
94 | }
95 |
--------------------------------------------------------------------------------
/assets/tosca/profiles/helm/1.0/js/kubernetes-resources-get.js:
--------------------------------------------------------------------------------
1 |
2 | const tosca = require('tosca.lib.utils');
3 |
4 | exports.plugin = function(resources) {
5 | for (let vertexId in clout.vertexes) {
6 | let vertex = clout.vertexes[vertexId];
7 | if (!tosca.isNodeTemplate(vertex, 'cloud.puccini.helm::Release'))
8 | continue;
9 | let nodeTemplate = vertex.properties;
10 |
11 | let tmp = puccini.temporaryFile('helm-chart-*.tar.gz');
12 |
13 | try {
14 | puccini.download(nodeTemplate.properties.chart, tmp);
15 |
16 | let name = nodeTemplate.properties.name || nodeTemplate.name;
17 | let namespace = nodeTemplate.properties.namespace;
18 |
19 | let args = [
20 | 'install',
21 | '--dry-run',
22 | '--output', 'json'
23 | ];
24 |
25 | if (nodeTemplate.properties.version)
26 | args.push('--version', nodeTemplate.properties.version);
27 |
28 | if (namespace)
29 | args.push('--namespace', namespace);
30 |
31 | if (nodeTemplate.properties.valuesUrl)
32 | args.push('--values', nodeTemplate.properties.valuesUrl);
33 |
34 | let values = nodeTemplate.properties.values;
35 | if (values)
36 | for (let key in values)
37 | args.push('--set-string', puccini.sprintf('%s=%s', key, values[key]));
38 |
39 | args.push(name)
40 | args.push(tmp);
41 |
42 | puccini.log.infof('helm %s', args.join(' '));
43 | let output = puccini.exec('helm', args);
44 | output = JSON.parse(output);
45 | //puccini.log.infof('%s', JSON.stringify(output, null, ' '));
46 | if (output.manifest) {
47 | let manifests = puccini.decode(output.manifest, 'yaml', true);
48 | for (let m = 0, l = manifests.length; m < l; m++) {
49 | let manifest = manifests[m];
50 | processManifest(manifest, name, namespace);
51 | resources.push(manifest);
52 | }
53 | }
54 | if (nodeTemplate.properties.hooks && output.hooks) {
55 | for (let h = 0, l = output.hooks.length; h < l; h++) {
56 | let manifest = puccini.decode(output.hooks[h].manifest, 'yaml');
57 | processManifest(manifest, name, namespace);
58 | resources.push(manifest);
59 | }
60 | }
61 | } finally {
62 | puccini.exec('rm', ['--force', tmp]);
63 | }
64 | }
65 | };
66 |
67 | function processManifest(manifest, name, namespace) {
68 | if (!manifest.metadata)
69 | manifest.metadata = {};
70 | if (!manifest.metadata.annotations)
71 | manifest.metadata.annotations = {};
72 | if (!manifest.metadata.annotations['meta.helm.sh/release-name'])
73 | manifest.metadata.annotations['meta.helm.sh/release-name'] = name;
74 | if (!manifest.metadata.annotations['meta.helm.sh/release-namespace'] && namespace)
75 | manifest.metadata.annotations['meta.helm.sh/release-namespace'] = namespace;
76 | }
77 |
--------------------------------------------------------------------------------
/controller/puccini.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | contextpkg "context"
5 | "errors"
6 | "io"
7 | "strings"
8 |
9 | "github.com/tliron/commonlog"
10 | "github.com/tliron/exturl"
11 | "github.com/tliron/go-ard"
12 | "github.com/tliron/go-transcribe"
13 | "github.com/tliron/kutil/problems"
14 | "github.com/tliron/kutil/terminal"
15 | cloutpkg "github.com/tliron/puccini/clout"
16 | "github.com/tliron/puccini/clout/js"
17 | "github.com/tliron/puccini/tosca/parser"
18 | )
19 |
20 | var pucciniLog = commonlog.GetLogger("turandot.puccini")
21 |
22 | var pucciniParser = parser.NewParser()
23 |
24 | func CompileTOSCA(context contextpkg.Context, url string, inputs map[string]ard.Value, writer io.Writer, urlContext *exturl.Context) error {
25 | if url_, err := urlContext.NewURL(url); err == nil {
26 | // TODO: bases
27 |
28 | parserContext := pucciniParser.NewContext()
29 | parserContext.URL = url_
30 | parserContext.Stylist = terminal.NewStylist(false)
31 | parserContext.Inputs = inputs
32 |
33 | if serviceTemplate, err := parserContext.Parse(context); err == nil {
34 | problems := parserContext.GetProblems()
35 | if problems.Empty() {
36 | if clout, err := serviceTemplate.Compile(); err == nil {
37 | return WriteClout(clout, writer)
38 | } else {
39 | return err
40 | }
41 | } else {
42 | return errors.New(problems.ToString(true))
43 | }
44 | } else {
45 | return err
46 | }
47 | } else {
48 | return err
49 | }
50 | }
51 |
52 | func ReadClout(reader io.Reader, urlContext *exturl.Context) (*cloutpkg.Clout, error) {
53 | if clout, err := cloutpkg.Read(reader, "yaml"); err == nil {
54 | execContext := js.ExecContext{
55 | Clout: clout,
56 | Problems: problems.NewProblems(nil),
57 | URLContext: urlContext,
58 | Format: "yaml",
59 | }
60 |
61 | if execContext.Resolve(); execContext.Problems.Empty() {
62 | return clout, nil
63 | } else {
64 | return nil, errors.New(execContext.Problems.ToString(true))
65 | }
66 | } else {
67 | return nil, err
68 | }
69 | }
70 |
71 | func WriteClout(clout *cloutpkg.Clout, writer io.Writer) error {
72 | return transcribe.NewTranscriber().SetWriter(writer).SetIndentSpaces(2).WriteYAML(clout)
73 | }
74 |
75 | func RequireCloutScriptlet(clout *cloutpkg.Clout, scriptletName string, arguments map[string]string, urlContext *exturl.Context) (string, error) {
76 | environment := js.NewEnvironment(scriptletName, pucciniLog, arguments, false, "yaml", false, false, false, "", urlContext)
77 | var builder strings.Builder
78 | environment.Stdout = &builder
79 | if _, err := environment.Require(clout, scriptletName, nil); err == nil {
80 | return builder.String(), nil
81 | } else {
82 | return "", err
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/turandot/commands/root.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "os/user"
5 | "path/filepath"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/tliron/commonlog"
9 | "github.com/tliron/kutil/util"
10 | "k8s.io/klog/v2"
11 | )
12 |
13 | var logTo string
14 | var verbose int
15 | var maxWidth int
16 | var colorize string
17 | var format string
18 | var strict bool
19 | var pretty bool
20 | var base64 bool
21 |
22 | var masterUrl string
23 | var kubeconfigPath string
24 | var kubeconfigContext string
25 | var clusterMode bool
26 | var namespace string
27 |
28 | func init() {
29 | var defaultKubeconfigPath string
30 | if u, err := user.Current(); err == nil {
31 | defaultKubeconfigPath = filepath.Join(u.HomeDir, ".kube", "config")
32 | }
33 |
34 | rootCommand.PersistentFlags().StringVarP(&logTo, "log", "l", "", "log to file (defaults to stderr)")
35 | rootCommand.PersistentFlags().CountVarP(&verbose, "verbose", "v", "add a log verbosity level (can be used twice)")
36 | rootCommand.PersistentFlags().IntVarP(&maxWidth, "width", "j", 0, "maximum output width (0 to use terminal width, -1 for no maximum)")
37 | rootCommand.PersistentFlags().StringVarP(&colorize, "colorize", "z", "true", "colorize output (boolean or \"force\")")
38 | rootCommand.PersistentFlags().StringVarP(&format, "format", "o", "", "output format (\"bare\", \"yaml\", \"json\", \"xjson\", \"xml\", \"cbor\", \"messagepack\", or \"go\")")
39 | rootCommand.PersistentFlags().BoolVarP(&strict, "strict", "y", false, "strict output (for \"yaml\" format only)")
40 | rootCommand.PersistentFlags().BoolVarP(&pretty, "pretty", "p", true, "prettify output")
41 | rootCommand.PersistentFlags().BoolVarP(&base64, "base64", "", false, "output base64 (for \"cbor\", \"messagepack\" formats)")
42 |
43 | rootCommand.PersistentFlags().StringVarP(&masterUrl, "master", "m", "", "address of the Kubernetes API server")
44 | rootCommand.PersistentFlags().StringVarP(&kubeconfigPath, "kubeconfig", "k", defaultKubeconfigPath, "path to Kubernetes configuration")
45 | rootCommand.PersistentFlags().StringVarP(&kubeconfigContext, "context", "x", "", "name of context in Kubernetes configuration")
46 | rootCommand.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "namespace (overrides context namespace in Kubernetes configuration)")
47 | }
48 |
49 | var rootCommand = &cobra.Command{
50 | Use: toolName,
51 | Short: "Control the Turandot operator",
52 | PersistentPreRun: func(cmd *cobra.Command, args []string) {
53 | util.InitializeColorization(colorize)
54 | commonlog.Initialize(verbose, logTo)
55 | if writer := commonlog.GetWriter(); writer != nil {
56 | klog.SetOutput(writer)
57 | }
58 | },
59 | }
60 |
61 | func Execute() {
62 | err := rootCommand.Execute()
63 | util.FailOnError(err)
64 | }
65 |
--------------------------------------------------------------------------------
/apis/applyconfiguration/turandot.puccini.cloud/v1alpha1/servicetemplatedirect.go:
--------------------------------------------------------------------------------
1 | // Code generated by applyconfiguration-gen. DO NOT EDIT.
2 |
3 | package v1alpha1
4 |
5 | // ServiceTemplateDirectApplyConfiguration represents an declarative configuration of the ServiceTemplateDirect type for use
6 | // with apply.
7 | type ServiceTemplateDirectApplyConfiguration struct {
8 | URL *string `json:"url,omitempty"`
9 | TLSSecret *string `json:"tlsSecret,omitempty"`
10 | TLSSecretDataKey *string `json:"tlsSecretDataKey,omitempty"`
11 | AuthSecret *string `json:"authSecret,omitempty"`
12 | }
13 |
14 | // ServiceTemplateDirectApplyConfiguration constructs an declarative configuration of the ServiceTemplateDirect type for use with
15 | // apply.
16 | func ServiceTemplateDirect() *ServiceTemplateDirectApplyConfiguration {
17 | return &ServiceTemplateDirectApplyConfiguration{}
18 | }
19 |
20 | // WithURL sets the URL field in the declarative configuration to the given value
21 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
22 | // If called multiple times, the URL field is set to the value of the last call.
23 | func (b *ServiceTemplateDirectApplyConfiguration) WithURL(value string) *ServiceTemplateDirectApplyConfiguration {
24 | b.URL = &value
25 | return b
26 | }
27 |
28 | // WithTLSSecret sets the TLSSecret field in the declarative configuration to the given value
29 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
30 | // If called multiple times, the TLSSecret field is set to the value of the last call.
31 | func (b *ServiceTemplateDirectApplyConfiguration) WithTLSSecret(value string) *ServiceTemplateDirectApplyConfiguration {
32 | b.TLSSecret = &value
33 | return b
34 | }
35 |
36 | // WithTLSSecretDataKey sets the TLSSecretDataKey field in the declarative configuration to the given value
37 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
38 | // If called multiple times, the TLSSecretDataKey field is set to the value of the last call.
39 | func (b *ServiceTemplateDirectApplyConfiguration) WithTLSSecretDataKey(value string) *ServiceTemplateDirectApplyConfiguration {
40 | b.TLSSecretDataKey = &value
41 | return b
42 | }
43 |
44 | // WithAuthSecret sets the AuthSecret field in the declarative configuration to the given value
45 | // and returns the receiver, so that objects can be built by chaining "With" function invocations.
46 | // If called multiple times, the AuthSecret field is set to the value of the last call.
47 | func (b *ServiceTemplateDirectApplyConfiguration) WithAuthSecret(value string) *ServiceTemplateDirectApplyConfiguration {
48 | b.AuthSecret = &value
49 | return b
50 | }
51 |
--------------------------------------------------------------------------------
/assets/kubernetes/custom-resource-definition.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1
2 | kind: CustomResourceDefinition
3 |
4 | metadata:
5 | name: services.turandot.puccini.cloud
6 |
7 | spec:
8 | group: turandot.puccini.cloud
9 | names:
10 | singular: service
11 | plural: services
12 | kind: Service
13 | listKind: ServiceList
14 | shortNames:
15 | - si # = ServIce? Service Instance?
16 | categories:
17 | - all # will appear in "kubectl get all"
18 | scope: Namespaced
19 | versions:
20 | - name: v1alpha1
21 | served: true
22 | storage: true # one and only one version must be marked with storage=true
23 | subresources: # requires CustomResourceSubresources feature gate enabled
24 | status: {}
25 | schema:
26 | openAPIV3Schema:
27 | type: object
28 | required: [ spec ]
29 | properties:
30 | spec:
31 | type: object
32 | required: [ serviceTemplateUrl ]
33 | properties:
34 | serviceTemplateUrl:
35 | type: string
36 | inputs:
37 | type: object
38 | nullable: true
39 | additionalProperties:
40 | type: string
41 | mode:
42 | type: string
43 | status:
44 | type: object
45 | properties:
46 | cloutPath:
47 | type: string
48 | cloutHash:
49 | type: string
50 | serviceTemplateUrl:
51 | type: string
52 | inputs:
53 | type: object
54 | nullable: true
55 | additionalProperties:
56 | type: string
57 | outputs:
58 | type: object
59 | nullable: true
60 | additionalProperties:
61 | type: string
62 | instantiationState:
63 | type: string
64 | enum:
65 | - NotInstantiated
66 | - Instantiating
67 | - Instantiated
68 | nodeStates:
69 | type: object
70 | nullable: true
71 | additionalProperties:
72 | type: object
73 | properties:
74 | mode:
75 | type: string
76 | state:
77 | type: string
78 | enum:
79 | - Accepted
80 | - Rejected
81 | - Achieved
82 | - Failed
83 | message:
84 | type: string
85 | mode:
86 | type: string
87 |
--------------------------------------------------------------------------------
/examples/telephony-network-service/artifacts/keypairs/admin@asterisk-vnf:
--------------------------------------------------------------------------------
1 | -----BEGIN OPENSSH PRIVATE KEY-----
2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
3 | NhAAAAAwEAAQAAAYEA6VeMyKiTXA/dOYoD/xIuJ8KPPiwnSBoAmFBDAY6OiSE8yIVUSoac
4 | I72FtvAyUOJr30R5MshpYMtiSm6zly2MqqBc8RPfVUYvncCNFFr0Nen0SITmAu19dNwcmH
5 | 8a6aZdi9u+bfQ8WB7265THyzOPdBChv3Vh3sh8n+UlNOOUuqxGV6XSVJw+Qj6xUnwMXrpq
6 | XBxqkzfBa81nH1/eO7ADhEwh1T7kzEJQr1hzLa7HhjIhA+jOXha4QKL8Jza60kbAlkhJOI
7 | 8hzWNOkkCGr6HrMnpmoRsSFwQu0m4hV8l6OQEOR08GyzTHKh4WtKb+aGbfECinXUQ98YUz
8 | YdTHB9pnWsAtTbBYaIriljQoIcB5kfxBzdNbDCsdtZiduPh2PhhskW8YE8sHxCc+UNNCWd
9 | 6xeCs9F+BJ3a9PfCVngu0OY/76ZM9oG5Rleovb5DMAa9cmNA2wSPEGiOQTOoNj4fiBVDay
10 | 9S1VmLFM2XqSTJOmE1LOvg4ccUuQ95v350y3M8WbAAAFkBz64Vsc+uFbAAAAB3NzaC1yc2
11 | EAAAGBAOlXjMiok1wP3TmKA/8SLifCjz4sJ0gaAJhQQwGOjokhPMiFVEqGnCO9hbbwMlDi
12 | a99EeTLIaWDLYkpus5ctjKqgXPET31VGL53AjRRa9DXp9EiE5gLtfXTcHJh/GummXYvbvm
13 | 30PFge9uuUx8szj3QQob91Yd7IfJ/lJTTjlLqsRlel0lScPkI+sVJ8DF66alwcapM3wWvN
14 | Zx9f3juwA4RMIdU+5MxCUK9Ycy2ux4YyIQPozl4WuECi/Cc2utJGwJZISTiPIc1jTpJAhq
15 | +h6zJ6ZqEbEhcELtJuIVfJejkBDkdPBss0xyoeFrSm/mhm3xAop11EPfGFM2HUxwfaZ1rA
16 | LU2wWGiK4pY0KCHAeZH8Qc3TWwwrHbWYnbj4dj4YbJFvGBPLB8QnPlDTQlnesXgrPRfgSd
17 | 2vT3wlZ4LtDmP++mTPaBuUZXqL2+QzAGvXJjQNsEjxBojkEzqDY+H4gVQ2svUtVZixTNl6
18 | kkyTphNSzr4OHHFLkPeb9+dMtzPFmwAAAAMBAAEAAAGAF6496GNLSS2G+v7ptuomavyQwv
19 | OCLQwOgWar1i2cg4gu/f6h9kpA8FOcjVtFD/ZakjcFwcSbBSq+1+TyLuZ9fxd1NzYYFTRh
20 | QrY22xj77bjHhNCP8z4c3E42TLehiJLMrZPoeIov0ZWa43+e4x1hcpr5f0+vVgestiFwLD
21 | TodCVloiF+/f05SEnKfx99teNfRefQXQ5sf79BtupoyE/tm3CKgnS2jZAxVgo47irtrs3J
22 | kfb/cocFvfGMe4JPZWWuli+Dy+ZX0/Zvg3cj/3uPn5El6p/KdxEOR1zjXHgwtmH9FFizfG
23 | xjfe6hnR1T8AZrltY/YsMMmd+rL4HDa9U5TZtPznzCQZDpKwtO1xFq3eS7Z3yIZPkxQmYq
24 | a9ViOfeQpxoIXFMfHApabngZJBse2or2fzXR+Q/siwhj0Uvg2PCXJAIMIHdWG58tfe8fwI
25 | J8METxJf+sItxfrC2p6DdIjQ4X7AaUvZGchvmLGqkTBIdutxGc6fnvnAcNCLftozFhAAAA
26 | wA438WVCw2OMMhShrjeRaODKd7vrQTv0P3WnLld36TC+j6qaDE5nZJn/okjckx6l6oMN7Y
27 | ScIYKyCJyu+XDk73skMG1sUw6nZzmekE6vlgkJ/v5MB+dawmoW02j88TcT1mQoVCvjmrrS
28 | kZN5MVm93yXrIHFPC8ah9hO/MUBnraOIUa7W8NrcJujsIob3VGmF8GW7Al7V0IB1GGsAkL
29 | XXeZEEwjPt15U0342MLUlDi0lALZ6dPK9GjYL5KwIb3N7K8AAAAMEA/GuPyylTnBTkv+6f
30 | GPD1t3/nTlSrtmaZbBmuT7OmUvAv5TnhFLnVEk1wA25gH1AhSmEfS0G3eIGwqgnwrMC5Lv
31 | NfDOpr6cDl0skxAAQdfkcY57LVyckAQL4j9leLH2r4/oGJASL3W/PKyaBW9eSIgiqDpAl9
32 | oxxS/2rJtOmvigEJd8PjCZ3RuNlBt46bE/XoEgmyVswkRSSDQ001ZkG5mOzRnOKqr4wcrm
33 | T7VqIME2A3E2Xgo1ySp7aOemqLfpCrAAAAwQDsprkQoVxTecYWRNaq1rbGRk4Qd5tEzi8J
34 | mZa8qUcUHKreuR+C1LVrcza7uzbi9bs46SAHwTFpaimROhPXNQq1axAAoFQZXDkKh6Urz3
35 | QVQ0FLIN8dcsXAn/BFvA1JLdTt2sH7Y8Prlu5RytFc9jxLzhuijePKG+l5f5PsvYE7AnD/
36 | yFlJaPy4wcNa2HJxsqRzL9riI5HqH3XFWoNl2BVak2Xc6RCYw37lru6/5XNyl+ZIiEmF2M
37 | MJx5RQBGAC/tEAAAAVZW1ibGVtcGFyYWRlQGZvdWNhdWx0AQIDBAUG
38 | -----END OPENSSH PRIVATE KEY-----
39 |
--------------------------------------------------------------------------------