mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-23 22:46:53 +00:00
OpenAPI: Application creation based on Application object (#924)
* OpenAPI: Application creation based on Application object Implemented Application creation OpenAPI based on new Application object To #913 * fix e2e problems * fix import issue
This commit is contained in:
@@ -783,6 +783,38 @@
|
||||
"type" : "integer"
|
||||
}
|
||||
}
|
||||
};
|
||||
defs["appfile.AppFile"] = {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"createTime" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"name" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"secrets" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : {
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"services" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : {
|
||||
"$ref" : "#/components/schemas/appfile.Service"
|
||||
}
|
||||
},
|
||||
"updateTime" : {
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
};
|
||||
defs["appfile.Service"] = {
|
||||
"type" : "object"
|
||||
};
|
||||
defs["config.Store"] = {
|
||||
"type" : "object"
|
||||
};
|
||||
defs["inline_response_200"] = {
|
||||
"type" : "object",
|
||||
@@ -1269,6 +1301,9 @@
|
||||
<li class="nav-fixed nav-header active" data-group="_"><a href="#api-_">API Summary</a></li>
|
||||
|
||||
<li class="nav-header" data-group="Applications"><a href="#api-Applications">API Methods - Applications</a></li>
|
||||
<li data-group="Applications" data-name="createApplication" class="">
|
||||
<a href="#api-Applications-createApplication">createApplication</a>
|
||||
</li>
|
||||
<li data-group="Applications" data-name="listApplications" class="">
|
||||
<a href="#api-Applications-listApplications">listApplications</a>
|
||||
</li>
|
||||
@@ -1316,6 +1351,404 @@
|
||||
<div id="sections">
|
||||
<section id="api-Applications">
|
||||
<h1>Applications</h1>
|
||||
<div id="api-Applications-createApplication">
|
||||
<article id="api-Applications-createApplication-0" data-group="User" data-name="createApplication" data-version="0">
|
||||
<div class="pull-left">
|
||||
<h1>createApplication</h1>
|
||||
<p>creates an application</p>
|
||||
</div>
|
||||
<div class="pull-right"></div>
|
||||
<div class="clearfix"></div>
|
||||
<p></p>
|
||||
<p class="marked"></p>
|
||||
<p></p>
|
||||
<br />
|
||||
<pre class="prettyprint language-html prettyprinted" data-type="post"><code><span class="pln">/envs/{envName}/apps</span></code></pre>
|
||||
<p>
|
||||
<h3>Usage and SDK Samples</h3>
|
||||
</p>
|
||||
<ul class="nav nav-tabs nav-tabs-examples">
|
||||
<li class="active"><a href="#examples-Applications-createApplication-0-curl">Curl</a></li>
|
||||
<li class=""><a href="#examples-Applications-createApplication-0-java">Java</a></li>
|
||||
<li class=""><a href="#examples-Applications-createApplication-0-android">Android</a></li>
|
||||
<!--<li class=""><a href="#examples-Applications-createApplication-0-groovy">Groovy</a></li>-->
|
||||
<li class=""><a href="#examples-Applications-createApplication-0-objc">Obj-C</a></li>
|
||||
<li class=""><a href="#examples-Applications-createApplication-0-javascript">JavaScript</a></li>
|
||||
<!--<li class=""><a href="#examples-Applications-createApplication-0-angular">Angular</a></li>-->
|
||||
<li class=""><a href="#examples-Applications-createApplication-0-csharp">C#</a></li>
|
||||
<li class=""><a href="#examples-Applications-createApplication-0-php">PHP</a></li>
|
||||
<li class=""><a href="#examples-Applications-createApplication-0-perl">Perl</a></li>
|
||||
<li class=""><a href="#examples-Applications-createApplication-0-python">Python</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="examples-Applications-createApplication-0-curl">
|
||||
<pre class="prettyprint"><code class="language-bsh">curl -X POST\
|
||||
-H "Accept: */*"\
|
||||
-H "Content-Type: */*"\
|
||||
"//127.0.0.1:38081/api/envs/{envName}/apps"</code></pre>
|
||||
</div>
|
||||
<div class="tab-pane" id="examples-Applications-createApplication-0-java">
|
||||
<pre class="prettyprint"><code class="language-java">import io.swagger.client.*;
|
||||
import io.swagger.client.auth.*;
|
||||
import io.swagger.client.model.*;
|
||||
import io.swagger.client.api.ApplicationsApi;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
public class ApplicationsApiExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
ApplicationsApi apiInstance = new ApplicationsApi();
|
||||
Appfile.AppFile body = ; // Appfile.AppFile | application parameters
|
||||
String envName = envName_example; // String | environment name
|
||||
try {
|
||||
inline_response_200 result = apiInstance.createApplication(body, envName);
|
||||
System.out.println(result);
|
||||
} catch (ApiException e) {
|
||||
System.err.println("Exception when calling ApplicationsApi#createApplication");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="examples-Applications-createApplication-0-android">
|
||||
<pre class="prettyprint"><code class="language-java">import io.swagger.client.api.ApplicationsApi;
|
||||
|
||||
public class ApplicationsApiExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationsApi apiInstance = new ApplicationsApi();
|
||||
Appfile.AppFile body = ; // Appfile.AppFile | application parameters
|
||||
String envName = envName_example; // String | environment name
|
||||
try {
|
||||
inline_response_200 result = apiInstance.createApplication(body, envName);
|
||||
System.out.println(result);
|
||||
} catch (ApiException e) {
|
||||
System.err.println("Exception when calling ApplicationsApi#createApplication");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
<!--
|
||||
<div class="tab-pane" id="examples-Applications-createApplication-0-groovy">
|
||||
<pre class="prettyprint language-json prettyprinted" data-type="json"><code>Coming Soon!</code></pre>
|
||||
</div> -->
|
||||
<div class="tab-pane" id="examples-Applications-createApplication-0-objc">
|
||||
<pre class="prettyprint"><code class="language-cpp">Appfile.AppFile *body = ; // application parameters
|
||||
String *envName = envName_example; // environment name
|
||||
|
||||
ApplicationsApi *apiInstance = [[ApplicationsApi alloc] init];
|
||||
|
||||
// creates an application
|
||||
[apiInstance createApplicationWith:body
|
||||
envName:envName
|
||||
completionHandler: ^(inline_response_200 output, NSError* error) {
|
||||
if (output) {
|
||||
NSLog(@"%@", output);
|
||||
}
|
||||
if (error) {
|
||||
NSLog(@"Error: %@", error);
|
||||
}
|
||||
}];
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="examples-Applications-createApplication-0-javascript">
|
||||
<pre class="prettyprint"><code class="language-js">var KubeVelaRestfulApi = require('kube_vela_restful_api');
|
||||
|
||||
var api = new KubeVelaRestfulApi.ApplicationsApi()
|
||||
var body = ; // {{Appfile.AppFile}} application parameters
|
||||
var envName = envName_example; // {{String}} environment name
|
||||
|
||||
var callback = function(error, data, response) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
} else {
|
||||
console.log('API called successfully. Returned data: ' + data);
|
||||
}
|
||||
};
|
||||
api.createApplication(bodyenvName, callback);
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
<!--<div class="tab-pane" id="examples-Applications-createApplication-0-angular">
|
||||
<pre class="prettyprint language-json prettyprinted" data-type="json"><code>Coming Soon!</code></pre>
|
||||
</div>-->
|
||||
<div class="tab-pane" id="examples-Applications-createApplication-0-csharp">
|
||||
<pre class="prettyprint"><code class="language-cs">using System;
|
||||
using System.Diagnostics;
|
||||
using IO.Swagger.Api;
|
||||
using IO.Swagger.Client;
|
||||
using IO.Swagger.Model;
|
||||
|
||||
namespace Example
|
||||
{
|
||||
public class createApplicationExample
|
||||
{
|
||||
public void main()
|
||||
{
|
||||
|
||||
var apiInstance = new ApplicationsApi();
|
||||
var body = new Appfile.AppFile(); // Appfile.AppFile | application parameters
|
||||
var envName = envName_example; // String | environment name
|
||||
|
||||
try
|
||||
{
|
||||
// creates an application
|
||||
inline_response_200 result = apiInstance.createApplication(body, envName);
|
||||
Debug.WriteLine(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Print("Exception when calling ApplicationsApi.createApplication: " + e.Message );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="examples-Applications-createApplication-0-php">
|
||||
<pre class="prettyprint"><code class="language-php"><?php
|
||||
require_once(__DIR__ . '/vendor/autoload.php');
|
||||
|
||||
$api_instance = new Swagger\Client\ApiApplicationsApi();
|
||||
$body = ; // Appfile.AppFile | application parameters
|
||||
$envName = envName_example; // String | environment name
|
||||
|
||||
try {
|
||||
$result = $api_instance->createApplication($body, $envName);
|
||||
print_r($result);
|
||||
} catch (Exception $e) {
|
||||
echo 'Exception when calling ApplicationsApi->createApplication: ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
?></code></pre>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="examples-Applications-createApplication-0-perl">
|
||||
<pre class="prettyprint"><code class="language-perl">use Data::Dumper;
|
||||
use WWW::SwaggerClient::Configuration;
|
||||
use WWW::SwaggerClient::ApplicationsApi;
|
||||
|
||||
my $api_instance = WWW::SwaggerClient::ApplicationsApi->new();
|
||||
my $body = WWW::SwaggerClient::Object::Appfile.AppFile->new(); # Appfile.AppFile | application parameters
|
||||
my $envName = envName_example; # String | environment name
|
||||
|
||||
eval {
|
||||
my $result = $api_instance->createApplication(body => $body, envName => $envName);
|
||||
print Dumper($result);
|
||||
};
|
||||
if ($@) {
|
||||
warn "Exception when calling ApplicationsApi->createApplication: $@\n";
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="examples-Applications-createApplication-0-python">
|
||||
<pre class="prettyprint"><code class="language-python">from __future__ import print_statement
|
||||
import time
|
||||
import swagger_client
|
||||
from swagger_client.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# create an instance of the API class
|
||||
api_instance = swagger_client.ApplicationsApi()
|
||||
body = # Appfile.AppFile | application parameters
|
||||
envName = envName_example # String | environment name
|
||||
|
||||
try:
|
||||
# creates an application
|
||||
api_response = api_instance.create_application(body, envName)
|
||||
pprint(api_response)
|
||||
except ApiException as e:
|
||||
print("Exception when calling ApplicationsApi->createApplication: %s\n" % e)</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Parameters</h2>
|
||||
|
||||
<div class="methodsubtabletitle">Path parameters</div>
|
||||
<table id="methodsubtable">
|
||||
<tr>
|
||||
<th width="150px">Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr><td style="width:150px;">envName*</td>
|
||||
<td>
|
||||
|
||||
|
||||
<div id="d2e199_createApplication_envName">
|
||||
<div class="json-schema-view">
|
||||
<div class="primitive">
|
||||
<span class="type">
|
||||
String
|
||||
</span>
|
||||
|
||||
<div class="inner description">
|
||||
environment name
|
||||
</div>
|
||||
</div>
|
||||
<div class="inner required">
|
||||
Required
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<div class="methodsubtabletitle">Body parameters</div>
|
||||
<table id="methodsubtable">
|
||||
<tr>
|
||||
<th width="150px">Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr><td style="width:150px;">body <span style="color:red;">*</span></td>
|
||||
<td>
|
||||
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var schemaWrapper = {
|
||||
"description" : "application parameters",
|
||||
"content" : {
|
||||
"*/*" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/appfile.AppFile"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required" : true
|
||||
};
|
||||
var schema = schemaWrapper.content["*/*"].schema;
|
||||
if (schema.$ref != null) {
|
||||
schema = defsParser.$refs.get(schema.$ref);
|
||||
} else {
|
||||
schemaWrapper.components = {};
|
||||
schemaWrapper.components.schemas = Object.assign({}, defs);
|
||||
$RefParser.dereference(schemaWrapper).catch(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
var view = new JSONSchemaView(schema,2,{isBodyParam: true});
|
||||
var result = $('#d2e199_createApplication_body');
|
||||
result.empty();
|
||||
result.append(view.render());
|
||||
});
|
||||
</script>
|
||||
<div id="d2e199_createApplication_body"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<h2>Responses</h2>
|
||||
<h3> Status: 200 - OK </h3>
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-examples" >
|
||||
<li class="active">
|
||||
<a data-toggle="tab" href="#responses-createApplication-200-schema">Schema</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" style='margin-bottom: 10px;'>
|
||||
<div class="tab-pane active" id="responses-createApplication-200-schema">
|
||||
<div id='responses-createApplication-200-schema-200' style="padding: 30px; border-left: 1px solid #eee; border-right: 1px solid #eee; border-bottom: 1px solid #eee;">
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var schemaWrapper = {
|
||||
"description" : "OK",
|
||||
"content" : {
|
||||
"*/*" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/inline_response_200"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var schema = schemaWrapper.content["*/*"].schema;
|
||||
if (schema.$ref != null) {
|
||||
schema = defsParser.$refs.get(schema.$ref);
|
||||
} else {
|
||||
schemaWrapper.components = {};
|
||||
schemaWrapper.components.schemas = Object.assign({}, defs);
|
||||
$RefParser.dereference(schemaWrapper).catch(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
//console.log(JSON.stringify(schema));
|
||||
var view = new JSONSchemaView(schema, 3);
|
||||
$('#responses-createApplication-200-schema-data').val(stringify(schema));
|
||||
var result = $('#responses-createApplication-200-schema-200');
|
||||
result.empty();
|
||||
result.append(view.render());
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<input id='responses-createApplication-200-schema-data' type='hidden' value=''></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3> Status: 500 - Internal Server Error </h3>
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-examples" >
|
||||
<li class="active">
|
||||
<a data-toggle="tab" href="#responses-createApplication-500-schema">Schema</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" style='margin-bottom: 10px;'>
|
||||
<div class="tab-pane active" id="responses-createApplication-500-schema">
|
||||
<div id='responses-createApplication-500-schema-500' style="padding: 30px; border-left: 1px solid #eee; border-right: 1px solid #eee; border-bottom: 1px solid #eee;">
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var schemaWrapper = {
|
||||
"description" : "Internal Server Error",
|
||||
"content" : {
|
||||
"*/*" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/inline_response_200"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var schema = schemaWrapper.content["*/*"].schema;
|
||||
if (schema.$ref != null) {
|
||||
schema = defsParser.$refs.get(schema.$ref);
|
||||
} else {
|
||||
schemaWrapper.components = {};
|
||||
schemaWrapper.components.schemas = Object.assign({}, defs);
|
||||
$RefParser.dereference(schemaWrapper).catch(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
//console.log(JSON.stringify(schema));
|
||||
var view = new JSONSchemaView(schema, 3);
|
||||
$('#responses-createApplication-500-schema-data').val(stringify(schema));
|
||||
var result = $('#responses-createApplication-500-schema-500');
|
||||
result.empty();
|
||||
result.append(view.render());
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<input id='responses-createApplication-500-schema-data' type='hidden' value=''></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
<hr>
|
||||
<div id="api-Applications-listApplications">
|
||||
<article id="api-Applications-listApplications-0" data-group="User" data-name="listApplications" data-version="0">
|
||||
<div class="pull-left">
|
||||
|
||||
@@ -7,12 +7,13 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/oam-dev/kubevela/e2e"
|
||||
"github.com/oam-dev/kubevela/pkg/server/apis"
|
||||
"github.com/oam-dev/kubevela/pkg/server/util"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
"github.com/oam-dev/kubevela/e2e"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/api"
|
||||
"github.com/oam-dev/kubevela/pkg/server/apis"
|
||||
"github.com/oam-dev/kubevela/pkg/server/util"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -30,20 +31,27 @@ var (
|
||||
Namespace: "env-e2e-world-modified",
|
||||
}
|
||||
|
||||
workloadType = "webservice"
|
||||
workloadName = "app-e2e-api-hello"
|
||||
workloadType = "webservice"
|
||||
applicationName = "app-e2e-api-hello"
|
||||
svcName = "svc-e2e-api-hello"
|
||||
|
||||
workloadRunBodyWithoutImageFlag = apis.WorkloadRunBody{
|
||||
EnvName: envHelloMeta.EnvName,
|
||||
WorkloadName: workloadName,
|
||||
WorkloadType: workloadType,
|
||||
Flags: []apis.CommonFlag{{Name: "port", Value: "80"}},
|
||||
applicationCreationBodyWithoutImageFlag = api.AppFile{
|
||||
Name: applicationName,
|
||||
Services: map[string]api.Service{
|
||||
svcName: map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
workloadRunBody = apis.WorkloadRunBody{
|
||||
EnvName: envHelloMeta.EnvName,
|
||||
WorkloadName: workloadName,
|
||||
WorkloadType: workloadType,
|
||||
Flags: []apis.CommonFlag{{Name: "image", Value: "nginx:1.9.4"}, {Name: "port", Value: "80"}},
|
||||
|
||||
applicationCreationBody = api.AppFile{
|
||||
Name: applicationName,
|
||||
Services: map[string]api.Service{
|
||||
svcName: map[string]interface{}{
|
||||
"type": workloadType,
|
||||
"image": "wordpress:php7.4-apache",
|
||||
"port": "80",
|
||||
"cpu": "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -165,10 +173,11 @@ var _ = ginkgo.Describe("API", func() {
|
||||
})
|
||||
|
||||
ginkgo.Context("Workloads", func() {
|
||||
ginkgo.It("run workload", func() {
|
||||
data, err := json.Marshal(&workloadRunBody)
|
||||
ginkgo.It("create an application", func() {
|
||||
data, err := json.Marshal(&applicationCreationBody)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
resp, err := http.Post(util.URL("/workloads/"), "application/json", strings.NewReader(string(data)))
|
||||
url := fmt.Sprintf("/envs/%s/apps/", envHelloMeta.EnvName)
|
||||
resp, err := http.Post(util.URL(url), "application/json", strings.NewReader(string(data)))
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
defer resp.Body.Close()
|
||||
result, err := ioutil.ReadAll(resp.Body)
|
||||
@@ -177,14 +186,16 @@ var _ = ginkgo.Describe("API", func() {
|
||||
err = json.Unmarshal(result, &r)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(http.StatusOK).Should(gomega.Equal(r.Code), string(result))
|
||||
output := fmt.Sprintf("App %s deployed", workloadName)
|
||||
output := fmt.Sprintf("application %s is successfully created", applicationName)
|
||||
gomega.Expect(r.Data.(string)).To(gomega.ContainSubstring(output))
|
||||
})
|
||||
|
||||
ginkgo.It("run workload without compulsory flag", func() {
|
||||
data, err := json.Marshal(&workloadRunBodyWithoutImageFlag)
|
||||
ginkgo.It("create an application without compulsory flag of a service", func() {
|
||||
data, err := json.Marshal(&applicationCreationBodyWithoutImageFlag)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
resp, err := http.Post(util.URL("/workloads/"), "application/json", strings.NewReader(string(data)))
|
||||
url := fmt.Sprintf("/envs/%s/apps/", envHelloMeta.EnvName)
|
||||
resp, err := http.Post(util.URL(url), "application/json", strings.NewReader(string(data)))
|
||||
// TODO(zzxwill) revise the check process if we need to work on https://github.com/oam-dev/kubevela/discussions/933
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
defer resp.Body.Close()
|
||||
result, err := ioutil.ReadAll(resp.Body)
|
||||
@@ -192,8 +203,8 @@ var _ = ginkgo.Describe("API", func() {
|
||||
var r apis.Response
|
||||
err = json.Unmarshal(result, &r)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(http.StatusInternalServerError).Should(gomega.Equal(r.Code))
|
||||
output := "required flag(s) \"image\" not set"
|
||||
gomega.Expect(http.StatusOK).Should(gomega.Equal(r.Code), string(result))
|
||||
output := fmt.Sprintf("application %s is successfully created", applicationName)
|
||||
gomega.Expect(r.Data.(string)).To(gomega.ContainSubstring(output))
|
||||
})
|
||||
|
||||
@@ -215,7 +226,7 @@ var _ = ginkgo.Describe("API", func() {
|
||||
})
|
||||
|
||||
ginkgo.It("should delete an application", func() {
|
||||
req, err := http.NewRequest("DELETE", util.URL("/envs/"+envHelloMeta.EnvName+"/apps/"+workloadRunBody.WorkloadName), nil)
|
||||
req, err := http.NewRequest("DELETE", util.URL("/envs/"+envHelloMeta.EnvName+"/apps/"+applicationName), nil)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/serverlib"
|
||||
)
|
||||
|
||||
// NewExportCommand will create command for exporting deploy manifests from an AppFile
|
||||
@@ -18,7 +19,7 @@ func NewExportCommand(c types.Args, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
types.TagCommandType: types.TypeStart,
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
o := &AppfileOptions{
|
||||
o := &serverlib.AppfileOptions{
|
||||
IO: ioStream,
|
||||
Env: &types.EnvMeta{},
|
||||
}
|
||||
@@ -26,7 +27,7 @@ func NewExportCommand(c types.Args, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, data, err := o.export(filePath, true)
|
||||
_, data, err := o.Export(filePath, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-190
@@ -1,29 +1,12 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
k8sjson "k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/api"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/template"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/serverlib"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -53,7 +36,7 @@ func NewUpCommand(c types.Args, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
o := &AppfileOptions{
|
||||
o := &serverlib.AppfileOptions{
|
||||
Kubecli: kubecli,
|
||||
IO: ioStream,
|
||||
Env: velaEnv,
|
||||
@@ -70,174 +53,3 @@ func NewUpCommand(c types.Args, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
cmd.Flags().StringP(appFilePath, "f", "", "specify file path for appfile")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// AppfileOptions is some configuration that modify options for an Appfile
|
||||
type AppfileOptions struct {
|
||||
Kubecli client.Client
|
||||
IO cmdutil.IOStreams
|
||||
Env *types.EnvMeta
|
||||
}
|
||||
|
||||
func saveRemoteAppfile(url string) (string, error) {
|
||||
body, err := common.HTTPGet(context.Background(), url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ext := filepath.Ext(url)
|
||||
dest := "Appfile"
|
||||
if ext == ".json" {
|
||||
dest = "vela.json"
|
||||
} else if ext == ".yaml" || ext == ".yml" {
|
||||
dest = "vela.yaml"
|
||||
}
|
||||
//nolint:gosec
|
||||
return dest, ioutil.WriteFile(dest, body, 0644)
|
||||
}
|
||||
|
||||
type buildResult struct {
|
||||
appFile *api.AppFile
|
||||
application *v1alpha2.Application
|
||||
scopes []oam.Object
|
||||
}
|
||||
|
||||
func (o *AppfileOptions) export(filePath string, quiet bool) (*buildResult, []byte, error) {
|
||||
var app *api.AppFile
|
||||
var err error
|
||||
if !quiet {
|
||||
o.IO.Info("Parsing vela appfile ...")
|
||||
}
|
||||
if filePath != "" {
|
||||
if strings.HasPrefix(filePath, "https://") || strings.HasPrefix(filePath, "http://") {
|
||||
filePath, err = saveRemoteAppfile(filePath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
app, err = api.LoadFromFile(filePath)
|
||||
} else {
|
||||
app, err = api.Load()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !quiet {
|
||||
o.IO.Info("Load Template ...")
|
||||
}
|
||||
|
||||
tm, err := template.Load()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
appHandler := appfile.NewApplication(app, tm)
|
||||
|
||||
// new
|
||||
retApplication, scopes, err := appHandler.BuildOAMApplication(o.Env, o.IO, appHandler.Tm, quiet)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var w bytes.Buffer
|
||||
|
||||
enc := k8sjson.NewYAMLSerializer(k8sjson.DefaultMetaFactory, nil, nil)
|
||||
err = enc.Encode(retApplication, &w)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("yaml encode application failed: %w", err)
|
||||
}
|
||||
w.WriteByte('\n')
|
||||
|
||||
for _, scope := range scopes {
|
||||
w.WriteString("---\n")
|
||||
err = enc.Encode(scope, &w)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("yaml encode scope (%s) failed: %w", scope.GetName(), err)
|
||||
}
|
||||
w.WriteByte('\n')
|
||||
}
|
||||
|
||||
result := &buildResult{
|
||||
appFile: app,
|
||||
application: retApplication,
|
||||
scopes: scopes,
|
||||
}
|
||||
return result, w.Bytes(), nil
|
||||
}
|
||||
|
||||
// Run starts an application according to Appfile
|
||||
func (o *AppfileOptions) Run(filePath string) error {
|
||||
result, data, err := o.export(filePath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deployFilePath := ".vela/deploy.yaml"
|
||||
o.IO.Infof("Writing deploy config to (%s)\n", deployFilePath)
|
||||
if err := os.MkdirAll(filepath.Dir(deployFilePath), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(deployFilePath, data, 0600); err != nil {
|
||||
return errors.Wrap(err, "write deploy config manifests failed")
|
||||
}
|
||||
|
||||
if err := o.saveToAppDir(result.appFile); err != nil {
|
||||
return errors.Wrap(err, "save to app dir failed")
|
||||
}
|
||||
|
||||
o.IO.Infof("\nApplying application ...\n")
|
||||
return o.ApplyApp(result.application, result.scopes)
|
||||
}
|
||||
|
||||
func (o *AppfileOptions) saveToAppDir(f *api.AppFile) error {
|
||||
app := &api.Application{AppFile: f}
|
||||
return appfile.Save(app, o.Env.Name)
|
||||
}
|
||||
|
||||
// ApplyApp applys config resources for the app.
|
||||
// It differs by create and update:
|
||||
// - for create, it displays app status along with information of url, metrics, ssh, logging.
|
||||
// - for update, it rolls out a canary deployment and prints its information. User can verify the canary deployment.
|
||||
// This will wait for user approval. If approved, it continues upgrading the whole; otherwise, it would rollback.
|
||||
func (o *AppfileOptions) ApplyApp(app *v1alpha2.Application, scopes []oam.Object) error {
|
||||
key := apitypes.NamespacedName{
|
||||
Namespace: app.Namespace,
|
||||
Name: app.Name,
|
||||
}
|
||||
o.IO.Infof("Checking if app has been deployed...\n")
|
||||
var tmpApp v1alpha2.Application
|
||||
err := o.Kubecli.Get(context.TODO(), key, &tmpApp)
|
||||
switch {
|
||||
case apierrors.IsNotFound(err):
|
||||
o.IO.Infof("App has not been deployed, creating a new deployment...\n")
|
||||
case err == nil:
|
||||
o.IO.Infof("App exists, updating existing deployment...\n")
|
||||
default:
|
||||
return err
|
||||
}
|
||||
if err := o.apply(app, scopes); err != nil {
|
||||
return err
|
||||
}
|
||||
o.IO.Infof(o.Info(app))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *AppfileOptions) apply(app *v1alpha2.Application, scopes []oam.Object) error {
|
||||
if err := appfile.Run(context.TODO(), o.Kubecli, app, scopes); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info shows the status of each service in the Appfile
|
||||
func (o *AppfileOptions) Info(app *v1alpha2.Application) string {
|
||||
appName := app.Name
|
||||
var appUpMessage = "✅ App has been deployed 🚀🚀🚀\n" +
|
||||
fmt.Sprintf(" Port forward: vela port-forward %s\n", appName) +
|
||||
fmt.Sprintf(" SSH: vela exec %s\n", appName) +
|
||||
fmt.Sprintf(" Logging: vela logs %s\n", appName) +
|
||||
fmt.Sprintf(" App status: vela status %s\n", appName)
|
||||
for _, comp := range app.Spec.Components {
|
||||
appUpMessage += fmt.Sprintf(" Service status: vela status %s --svc %s\n", appName, comp.Name)
|
||||
}
|
||||
return appUpMessage
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/serverlib"
|
||||
)
|
||||
|
||||
func TestUp(t *testing.T) {
|
||||
@@ -23,7 +24,7 @@ func TestUp(t *testing.T) {
|
||||
Namespace: "env-up",
|
||||
Issuer: "up",
|
||||
}
|
||||
o := AppfileOptions{
|
||||
o := serverlib.AppfileOptions{
|
||||
Kubecli: client,
|
||||
IO: ioStream,
|
||||
Env: &env,
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
var _ = Describe("CRD without definition can run in an ApplicationConfiguration", func() {
|
||||
ctx := context.Background()
|
||||
It("run workload and trait without CRD", func() {
|
||||
It("create an application without CRD", func() {
|
||||
|
||||
By("Creating CRD foo.crdtest1.com")
|
||||
// Create a crd for appconfig dependency test
|
||||
|
||||
@@ -34,17 +34,6 @@ type CommonFlag struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// WorkloadRunBody used for restful API arguments for run workload for dashboard restful API server
|
||||
type WorkloadRunBody struct {
|
||||
EnvName string `json:"envName"`
|
||||
WorkloadType string `json:"workloadType"`
|
||||
WorkloadName string `json:"workloadName"`
|
||||
AppName string `json:"appName,omitempty"`
|
||||
Flags []CommonFlag `json:"flags"`
|
||||
Staging bool `json:"staging,omitempty"`
|
||||
Traits []TraitBody `json:"traits,omitempty"`
|
||||
}
|
||||
|
||||
// WorkloadMeta store workload metadata for dashboard restful API server
|
||||
type WorkloadMeta struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/api"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/server/util"
|
||||
"github.com/oam-dev/kubevela/pkg/serverlib"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/env"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
env2 "github.com/oam-dev/kubevela/pkg/utils/env"
|
||||
)
|
||||
|
||||
// UpdateApps is placeholder for updating applications
|
||||
@@ -57,7 +63,7 @@ func (s *APIServer) ListApps(c *gin.Context) {
|
||||
util.AssembleResponse(c, applicationMetaList, nil)
|
||||
}
|
||||
|
||||
// DeleteApps deletes an application by the namespacedname in the gin.Context
|
||||
// DeleteApps deletes an application by the namespaced name in the gin.Context
|
||||
func (s *APIServer) DeleteApps(c *gin.Context) {
|
||||
envName := c.Param("envName")
|
||||
envMeta, err := env.GetEnvByName(envName)
|
||||
@@ -75,3 +81,43 @@ func (s *APIServer) DeleteApps(c *gin.Context) {
|
||||
message, err := o.DeleteApp()
|
||||
util.AssembleResponse(c, message, err)
|
||||
}
|
||||
|
||||
// CreateApplication creates an application
|
||||
// @tags applications
|
||||
// @ID CreateApplication
|
||||
// @Summary creates an application
|
||||
// @Param envName path string true "environment name"
|
||||
// @Param body body appfile.AppFile true "application parameters"
|
||||
// @Success 200 {object} apis.Response{code=int,data=string}
|
||||
// @Failure 500 {object} apis.Response{code=int,data=string}
|
||||
// @Router /envs/{envName}/apps [post]
|
||||
func (s *APIServer) CreateApplication(c *gin.Context) {
|
||||
var body api.AppFile
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
util.HandleError(c, util.InvalidArgument, "the application creation request body is invalid")
|
||||
return
|
||||
}
|
||||
env, err := env2.GetEnvByName(c.Param("envName"))
|
||||
if err != nil {
|
||||
util.HandleError(c, util.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
|
||||
o := &serverlib.AppfileOptions{
|
||||
Kubecli: s.KubeClient,
|
||||
IO: ioStream,
|
||||
Env: env,
|
||||
}
|
||||
buildResult, data, err := o.ExportFromAppFile(&body, false)
|
||||
if err != nil {
|
||||
util.HandleError(c, util.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
err = o.BaseAppFileRun(buildResult, data)
|
||||
if err != nil {
|
||||
util.HandleError(c, util.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("application %s is successfully created", body.Name)
|
||||
util.AssembleResponse(c, msg, nil)
|
||||
}
|
||||
|
||||
@@ -518,6 +518,75 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"applications"
|
||||
],
|
||||
"summary": "creates an application",
|
||||
"operationId": "CreateApplication",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "environment name",
|
||||
"name": "envName",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "application parameters",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/appfile.AppFile"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/apis.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/apis.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -622,6 +691,39 @@ var doc = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"appfile.AppFile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/appfile.Service"
|
||||
}
|
||||
},
|
||||
"updateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appfile.Service": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"config.Store": {
|
||||
"type": "object"
|
||||
},
|
||||
"v1alpha2.ApplicationConfiguration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -503,6 +503,75 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"applications"
|
||||
],
|
||||
"summary": "creates an application",
|
||||
"operationId": "CreateApplication",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "environment name",
|
||||
"name": "envName",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "application parameters",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/appfile.AppFile"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/apis.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/apis.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -607,6 +676,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"appfile.AppFile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/appfile.Service"
|
||||
}
|
||||
},
|
||||
"updateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appfile.Service": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"config.Store": {
|
||||
"type": "object"
|
||||
},
|
||||
"v1alpha2.ApplicationConfiguration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -66,6 +66,28 @@ definitions:
|
||||
code:
|
||||
type: integer
|
||||
type: object
|
||||
appfile.AppFile:
|
||||
properties:
|
||||
createTime:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
services:
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/appfile.Service'
|
||||
type: object
|
||||
updateTime:
|
||||
type: string
|
||||
type: object
|
||||
appfile.Service:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
config.Store:
|
||||
type: object
|
||||
v1alpha2.ApplicationConfiguration:
|
||||
properties:
|
||||
spec:
|
||||
@@ -731,4 +753,44 @@ paths:
|
||||
summary: list all applications
|
||||
tags:
|
||||
- applications
|
||||
post:
|
||||
operationId: CreateApplication
|
||||
parameters:
|
||||
- description: environment name
|
||||
in: path
|
||||
name: envName
|
||||
required: true
|
||||
type: string
|
||||
- description: application parameters
|
||||
in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/appfile.AppFile'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/apis.Response'
|
||||
- properties:
|
||||
code:
|
||||
type: integer
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/apis.Response'
|
||||
- properties:
|
||||
code:
|
||||
type: integer
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
summary: creates an application
|
||||
tags:
|
||||
- applications
|
||||
swagger: "2.0"
|
||||
|
||||
+1
-1
@@ -80,6 +80,7 @@ func (s *APIServer) setupRoute(staticPath string) http.Handler {
|
||||
apps.GET("/", s.ListApps)
|
||||
apps.GET("", s.ListApps)
|
||||
apps.DELETE("/:appName", s.DeleteApps)
|
||||
apps.POST("/", s.CreateApplication)
|
||||
|
||||
// component related operation
|
||||
components := apps.Group("/:appName/components")
|
||||
@@ -101,7 +102,6 @@ func (s *APIServer) setupRoute(staticPath string) http.Handler {
|
||||
// workload related api
|
||||
workload := api.Group(util.WorkloadDefinitionPath)
|
||||
{
|
||||
workload.POST("/", s.CreateWorkload)
|
||||
workload.GET("/:workloadName", s.GetWorkload)
|
||||
workload.PUT("/:workloadName", s.UpdateWorkload)
|
||||
workload.GET("/", s.ListWorkload)
|
||||
|
||||
@@ -1,65 +1,14 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/plugins"
|
||||
"github.com/oam-dev/kubevela/pkg/server/apis"
|
||||
"github.com/oam-dev/kubevela/pkg/server/util"
|
||||
"github.com/oam-dev/kubevela/pkg/serverlib"
|
||||
env2 "github.com/oam-dev/kubevela/pkg/utils/env"
|
||||
)
|
||||
|
||||
// CreateWorkload creates a workload
|
||||
func (s *APIServer) CreateWorkload(c *gin.Context) {
|
||||
var body apis.WorkloadRunBody
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
util.HandleError(c, util.InvalidArgument, "the workload run request body is invalid")
|
||||
return
|
||||
}
|
||||
fs := pflag.NewFlagSet("workload", pflag.ContinueOnError)
|
||||
for _, f := range body.Flags {
|
||||
fs.String(f.Name, f.Value, "")
|
||||
}
|
||||
envName := body.EnvName
|
||||
|
||||
appObj, err := serverlib.BaseComplete(envName, body.WorkloadName, body.AppName, fs, body.WorkloadType)
|
||||
if err != nil {
|
||||
util.HandleError(c, util.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
env, err := env2.GetEnvByName(envName)
|
||||
if err != nil {
|
||||
util.HandleError(c, util.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
|
||||
msg, err := serverlib.BaseRun(body.Staging, appObj, s.KubeClient, env, io)
|
||||
if err != nil {
|
||||
util.HandleError(c, util.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if len(body.Traits) == 0 {
|
||||
util.AssembleResponse(c, msg, err)
|
||||
return
|
||||
}
|
||||
for _, t := range body.Traits {
|
||||
t.AppName = body.AppName
|
||||
t.ComponentName = body.WorkloadName
|
||||
msg, err = s.DoAttachTrait(c, t)
|
||||
if err != nil {
|
||||
util.HandleError(c, util.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
util.AssembleResponse(c, msg, err)
|
||||
}
|
||||
|
||||
// UpdateWorkload updates a workload
|
||||
func (s *APIServer) UpdateWorkload(c *gin.Context) {
|
||||
}
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
package serverlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
corev1alpha2 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/api"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/template"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/server/apis"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
// nolint:golint
|
||||
@@ -31,6 +42,20 @@ const (
|
||||
type componentMetaList []apis.ComponentMeta
|
||||
type applicationMetaList []apis.ApplicationMeta
|
||||
|
||||
// AppfileOptions is some configuration that modify options for an Appfile
|
||||
type AppfileOptions struct {
|
||||
Kubecli client.Client
|
||||
IO cmdutil.IOStreams
|
||||
Env *types.EnvMeta
|
||||
}
|
||||
|
||||
// BuildResult is the export struct from AppFile yaml or AppFile object
|
||||
type BuildResult struct {
|
||||
appFile *api.AppFile
|
||||
application *v1alpha2.Application
|
||||
scopes []oam.Object
|
||||
}
|
||||
|
||||
func (comps componentMetaList) Len() int {
|
||||
return len(comps)
|
||||
}
|
||||
@@ -298,3 +323,172 @@ func GetServicesWhenDescribingApplication(cmd *cobra.Command, app *api.Applicati
|
||||
}
|
||||
return targetServices, nil
|
||||
}
|
||||
|
||||
func saveRemoteAppfile(url string) (string, error) {
|
||||
body, err := common.HTTPGet(context.Background(), url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ext := filepath.Ext(url)
|
||||
dest := "Appfile"
|
||||
if ext == ".json" {
|
||||
dest = "vela.json"
|
||||
} else if ext == ".yaml" || ext == ".yml" {
|
||||
dest = "vela.yaml"
|
||||
}
|
||||
//nolint:gosec
|
||||
return dest, ioutil.WriteFile(dest, body, 0644)
|
||||
}
|
||||
|
||||
// ExportFromAppFile exports Application from appfile object
|
||||
func (o *AppfileOptions) ExportFromAppFile(app *api.AppFile, quiet bool) (*BuildResult, []byte, error) {
|
||||
tm, err := template.Load()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
appHandler := appfile.NewApplication(app, tm)
|
||||
|
||||
// new
|
||||
retApplication, scopes, err := appHandler.BuildOAMApplication(o.Env, o.IO, appHandler.Tm, quiet)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var w bytes.Buffer
|
||||
|
||||
options := json.SerializerOptions{Yaml: true, Pretty: false, Strict: false}
|
||||
enc := json.NewSerializerWithOptions(json.DefaultMetaFactory, nil, nil, options)
|
||||
err = enc.Encode(retApplication, &w)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("yaml encode application failed: %w", err)
|
||||
}
|
||||
w.WriteByte('\n')
|
||||
|
||||
for _, scope := range scopes {
|
||||
w.WriteString("---\n")
|
||||
err = enc.Encode(scope, &w)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("yaml encode scope (%s) failed: %w", scope.GetName(), err)
|
||||
}
|
||||
w.WriteByte('\n')
|
||||
}
|
||||
|
||||
result := &BuildResult{
|
||||
appFile: app,
|
||||
application: retApplication,
|
||||
scopes: scopes,
|
||||
}
|
||||
return result, w.Bytes(), nil
|
||||
}
|
||||
|
||||
// Export export Application object from the path of Appfile
|
||||
func (o *AppfileOptions) Export(filePath string, quiet bool) (*BuildResult, []byte, error) {
|
||||
var app *api.AppFile
|
||||
var err error
|
||||
if !quiet {
|
||||
o.IO.Info("Parsing vela appfile ...")
|
||||
}
|
||||
if filePath != "" {
|
||||
if strings.HasPrefix(filePath, "https://") || strings.HasPrefix(filePath, "http://") {
|
||||
filePath, err = saveRemoteAppfile(filePath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
app, err = api.LoadFromFile(filePath)
|
||||
} else {
|
||||
app, err = api.Load()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !quiet {
|
||||
o.IO.Info("Load Template ...")
|
||||
}
|
||||
return o.ExportFromAppFile(app, quiet)
|
||||
}
|
||||
|
||||
// Run starts an application according to Appfile
|
||||
func (o *AppfileOptions) Run(filePath string) error {
|
||||
result, data, err := o.Export(filePath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return o.BaseAppFileRun(result, data)
|
||||
}
|
||||
|
||||
// BaseAppFileRun starts an application according to Appfile
|
||||
func (o *AppfileOptions) BaseAppFileRun(result *BuildResult, data []byte) error {
|
||||
deployFilePath := ".vela/deploy.yaml"
|
||||
o.IO.Infof("Writing deploy config to (%s)\n", deployFilePath)
|
||||
if err := os.MkdirAll(filepath.Dir(deployFilePath), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(deployFilePath, data, 0600); err != nil {
|
||||
return errors.Wrap(err, "write deploy config manifests failed")
|
||||
}
|
||||
|
||||
if err := o.saveToAppDir(result.appFile); err != nil {
|
||||
return errors.Wrap(err, "save to app dir failed")
|
||||
}
|
||||
|
||||
o.IO.Infof("\nApplying application ...\n")
|
||||
return o.ApplyApp(result.application, result.scopes)
|
||||
}
|
||||
|
||||
func (o *AppfileOptions) saveToAppDir(f *api.AppFile) error {
|
||||
app := &api.Application{AppFile: f}
|
||||
return appfile.Save(app, o.Env.Name)
|
||||
}
|
||||
|
||||
// ApplyApp applys config resources for the app.
|
||||
// It differs by create and update:
|
||||
// - for create, it displays app status along with information of url, metrics, ssh, logging.
|
||||
// - for update, it rolls out a canary deployment and prints its information. User can verify the canary deployment.
|
||||
// This will wait for user approval. If approved, it continues upgrading the whole; otherwise, it would rollback.
|
||||
func (o *AppfileOptions) ApplyApp(app *v1alpha2.Application, scopes []oam.Object) error {
|
||||
key := apitypes.NamespacedName{
|
||||
Namespace: app.Namespace,
|
||||
Name: app.Name,
|
||||
}
|
||||
o.IO.Infof("Checking if app has been deployed...\n")
|
||||
var tmpApp v1alpha2.Application
|
||||
err := o.Kubecli.Get(context.TODO(), key, &tmpApp)
|
||||
switch {
|
||||
case apierrors.IsNotFound(err):
|
||||
o.IO.Infof("App has not been deployed, creating a new deployment...\n")
|
||||
case err == nil:
|
||||
o.IO.Infof("App exists, updating existing deployment...\n")
|
||||
default:
|
||||
return err
|
||||
}
|
||||
if err := o.apply(app, scopes); err != nil {
|
||||
return err
|
||||
}
|
||||
o.IO.Infof(o.Info(app))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *AppfileOptions) apply(app *v1alpha2.Application, scopes []oam.Object) error {
|
||||
if err := appfile.Run(context.TODO(), o.Kubecli, app, scopes); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info shows the status of each service in the Appfile
|
||||
func (o *AppfileOptions) Info(app *v1alpha2.Application) string {
|
||||
appName := app.Name
|
||||
var appUpMessage = "✅ App has been deployed 🚀🚀🚀\n" +
|
||||
fmt.Sprintf(" Port forward: vela port-forward %s\n", appName) +
|
||||
fmt.Sprintf(" SSH: vela exec %s\n", appName) +
|
||||
fmt.Sprintf(" Logging: vela logs %s\n", appName) +
|
||||
fmt.Sprintf(" App status: vela status %s\n", appName)
|
||||
for _, comp := range app.Spec.Components {
|
||||
appUpMessage += fmt.Sprintf(" Service status: vela status %s --svc %s\n", appName, comp.Name)
|
||||
}
|
||||
return appUpMessage
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user