Implementation of front-end automatic packaging and deployment based on Python

Posted by lcy on Tue, 11 Jan 2022 08:22:45 +0100

preface
Life is short. I use python~
As a full-time front-end developer, in order to help solve some cumbersome work in my current work (mainly dealing with excel data) and liberate programmers' hands, I just entered the pit of python a while ago. After all, it is also a tool language. I have joined children's programming. Ha ha!
background
Practice is the only standard to test learning achievements!

During my study, I have been thinking about how to combine the learning theory with my knowledge to solve or deal with practical problems, so I have the idea of front-end automatic packaging and deployment.

As soon as possible, in recent years, there have been an endless stream of tools on Automated Deployment on the market, such as Jenkins, which is more popular at present. Nevertheless, I still want to try it myself~

Environment configuration
When you are a beginner, you must not aim high and do low. Set a small goal for yourself and realize the simplest version first.
If a worker wants to do well, he must first sharpen his tools. The configuration of the development environment is the first step of development.

I won't repeat the installation configuration of python.

In order to facilitate the test, I installed centos system locally using VM virtual machine, and installed and configured nginx as the server.

Difficulty analysis

To realize packaging, the core needs to consider the following two issues:

  • How to execute the front-end packaging command npm run build in the python script (vue project is taken as the test here)
  • In the python script, how to connect to the server and upload the packaged problem to the specified directory of the server

Theoretical verification
According to the information, the os module in python provides rich methods to process files and directories. The system() function in the os module can easily run other programs or scripts. Its syntax is as follows:
os.system(command)
Command the command to be executed is equivalent to the command entered in the cmd window of Windows. If you want to pass parameters to a program or script, you can use spaces to separate the program and multiple parameters. If the result returned by this method is 0, it indicates that the command is executed successfully, and other values indicate an error.

This solves the first problem.

For server connection, you can use a third-party module paramiko of python, which implements the SSHv2 protocol and allows us to directly use the SSH protocol to perform operations on remote servers,
In this way, the above two difficulties will be solved and we can start work.
Small trial ox knife

First, define a class SSHConnect, and we will improve the subsequent methods in this class
class SSHConnect:

    # Define a private variable to save the ssh connection channel and initialize it to None
    __transport = None

Initial constructor

We need to define the parameters we need in the constructor to initialize our connection

# Initialize constructor (host, user name, password, port, default 22)
def __init__(self, hostname, username, password, port=22):
    self.hostname = hostname
    self.port = port
    self.username = username
    self.password = password
    # Create ssh connection channel
    self.connect()

Establish ssh connection channel

We finally call a connect method in the constructor to establish the ssh connection channel. Now let's implement it concretely.

# Establish an ssh connection channel and bind it to__ On transport
def connect(self):
    try:
        # Set the remote host address and port for SSH connection
        self.__transport = paramiko.Transport((self.hostname, self.port))
        # Connect to SSH server through user name and password
        self.__transport.connect(username=self.username, password=self.password)
    except Exception as e:
        # link error 
        print(e)

Perform packaging
Now we need to create a packaging method, execute the npm run build command, and use our OS System method, the input parameter is work_path is the directory where the packaged project is located

Front end packaging (enter work_path as project directory)

def build(self, work_path):

    # Start packing
    print('########### run build ############')
    # Packaging command
    cmd = 'npm run build'
    # Switch to the desired project directory
    os.chdir(work_path)
    # Execute the package command under the current project directory
    if os.system(cmd) == 0:
        # Packaging complete
        print('########### build complete ############')

Only one thing to note is to pass the OS The chdir (work_path) method switches to the directory where the project is located and packages the current project.
File upload
After packaging, we need to upload the files in the packaged dist folder to the server. Therefore, we need to create a file upload method through paramiko The sftpclient method creates sftp to complete

This method requires two parameters. One is the dist path local after the local project is packaged_ Path, and the other is the target directory to be uploaded to the server_ path

# File upload
def upload(self, local_path, target_path):

    # Judgment path problem
    if not os.path.exists(local_path):
        return print('local path is not exist')

    print('File uploading...')

    # Instantiate an sftp object to specify the connected channel
    sftp = paramiko.SFTPClient.from_transport(self.__transport)
    # Packaged file path
    local_path = os.path.join(local_path, 'dist')
    # Local path conversion, convert \ under windows to/
    local_path = '/'.join(local_path.split('\\'))
    # Recursive upload file
    self.upload_file(sftp, local_path, target_path)

    print('File upload complete...')
    # Close connection
    self.close()

To facilitate operation, you need to convert the path separator \ in windows to the separator under linux/
In addition, the other two methods are called upload_. File and close. The definition of the close method is very simple and can be called directly__ transport. The close () method is sufficient

# Close connection
def close(self):
    self.__transport.close()

Considering that our static is not a file but a folder, we need to recursively traverse and copy it to the server, so we define upload_ The file method is specifically responsible for this.
Execute linux commands
As mentioned above, we need to recursively traverse static and upload it to the server. The directory structure uploaded to the server must be consistent with the original static. Therefore, the operation on the server must be indispensable. We need to execute linux commands. We need an exec method to realize this function. The input parameter is linux commands

Execute linux commands

def exec(self, command):

    
    # Create ssh client
    ssh = paramiko.SSHClient()
    # Specifies the connected channel
    ssh._transport = self.__transport
    
    # Call Exec_ The command method executes the command
    stdin, stdout, stderr = ssh.exec_command(command)
    
    # Get the command result. The return is binary and needs to be encoded
    res = stdout.read().decode('utf-8')
    # Get error information
    error = stderr.read().decode('utf-8')
    
    # If nothing goes wrong
    if error.strip():
        # Return error message
        return error
    else:
        # Return results
        return res
    

Now you can connect to the server to test the correctness of this method

    ssh = SSHConnect(hostname='x.x.x.x', username='root', password='xxx')
    print(ssh.exec(r'df -h'))

We connect to the server and try to call the df -h command in linux to check the disk usage of our file system. If nothing happens, we will see the information returned by the console
ps: the r in front of the command df -h is to prevent the python interpreter from escaping

Recursive upload file
After the preparations are done, we can implement our recursive upload method upload_file, which mainly uploads the local file to the corresponding server through the put method of the sftp object created earlier

# Recursive upload file
def upload_file(self, sftp, local_path, target_path):
    # Determine whether the current path is a folder
    if not os.path.isdir(local_path):
        # If it is a file, get the file name
        file_name = os.path.basename(local_path)
        # Check if the server folder exists
        self.check_remote_dir(sftp, target_path)
        # Server create file
        target_file_path = os.path.join(target_path, file_name).replace('\\', '/')
        # Upload to server
        sftp.put(local_path, target_file_path)
    else:
        # View sub files under the current folder
        file_list = os.listdir(local_path)
        # Traverse sub files
        for p in file_list:
            # Splice current file path
            current_local_path = os.path.join(local_path, p).replace('\\', '/')
            # Splice server file path
            current_target_path = os.path.join(target_path, p).replace('\\', '/')
            # If it is already a file, the server does not need to create a folder
            if os.path.isfile(current_local_path):
                # Extract the folder where the current file is located
                current_target_path = os.path.split(current_target_path)[0]
            # Recursive judgment
            self.upload_file(sftp, current_local_path, current_target_path)

A check is added to the above method_ remote_ Dir method is used to check whether a folder already exists on the server. If the server does not, we will create one. The definition is as follows:

# Create server folder
def check_remote_dir(self, sftp, target_path):
    try:
        # Determine whether the folder exists
        sftp.stat(target_path)
    except IOError:
        # create folder
        self.exec(r'mkdir -p %s ' % target_path)

Very clever use of SFTP The stat method looks at the file information to distinguish.
Merge process, auto publish
Now that we have implemented the basic methods, we need to merge them into auto_ In the deploy method, automatic publishing is really realized.

# Automated packaging deployment
def auto_deploy(self, local_path, target_path):
    # Package build
    self.build(local_path)
    # File upload
    self.upload(local_path, target_path)

ok ~ let's call auto_ The deploy method tests:

if __name__ == '__main__':
    # Project directory
    project_path = r'D:\learn\vue-demo'
    # Server directory
    remote_path = r'/www/project'
    
    # instantiation 
    ssh = SSHConnect(hostname='x.x.x.x', username='root', password='xxx')
    # Automatic package deployment
    ssh.auto_deploy(project_path, remote_path)

If everything goes well, you can see that the console output is successful!!

Then check whether the server file has been uploaded successfully.

And try to visit my home page!

Perfect!
Congratulations! You've got this skill. Give it a compliment!
Server empty
At this point, our functions have been basically completed, but there is still a small problem left. If we continue iterative optimization, if we do not clear the server directory, more and more old files will accumulate and occupy the server space. Therefore, we need to clear them before packaging and uploading.

You might as well define a clear_remote_dir method to implement this function

# Empty Folder 
def clear_remote_dir(self, target_path):
    if target_path[-1] == '/':
        cmd = f'rm -rf {target_path}*'
    else:
        cmd = f'rm -rf {target_path}/*'
    self.exec(cmd)

Then add it to auto_ Self. In delpoy method Just before upload~
epilogue
It's just a small tool. For me, this process is a small practice of python. It's also quite rewarding.
For the above code, you can also use sys Argv realizes the real cmd call through command line parameter parsing. I won't repeat it here. Interested partners can practice it by themselves.
I can see that python is concise and elegant in syntax, which makes me feel very comfortable. Personally, it may be used as a tool language to solve practical problems to the greatest extent.
Life is short, I use python;

Topics: Python